2015-07-10 10:35:33 +02:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2015, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
2015-10-25 18:49:35 +01:00
//
/ * g l o b a l B a c k b o n e , C r y p t o J S , c r y p t o , d e f i n e , w i n d o w , j Q u e r y , s e t T i m e o u t , c l e a r T i m e o u t , d o c u m e n t , t e m p l a t e s , _ ,
$iq , $msg , $pres , $build , DSA , OTR , Strophe , moment , utils , b64 _sha1 , locales * /
2015-07-10 10:35:33 +02:00
2012-09-21 16:04:57 +02:00
( function ( root , factory ) {
2013-02-20 17:21:07 +01:00
if ( typeof define === 'function' && define . amd ) {
2015-07-10 10:35:33 +02:00
// AMD module loading
// ------------------
// When using require.js, two modules are loaded as dependencies.
//
// * **converse-dependencies**: A list of dependencies on which converse.js
// depends. The path to this module is in main.js and the module itself can
//
// * **converse-templates**: The HTML templates used by converse.js.
//
// The dependencies are then split up and passed into the factory function, which
// contains and instantiates converse.js.
2013-12-30 20:27:57 +01:00
define ( "converse" ,
[ "converse-dependencies" , "converse-templates" ] ,
2014-10-10 11:05:16 +02:00
function ( dependencies , templates ) {
2015-02-03 22:39:03 +01:00
return factory (
templates ,
dependencies . jQuery ,
dependencies . $iq ,
dependencies . $msg ,
dependencies . $pres ,
dependencies . $build ,
dependencies . otr ? dependencies . otr . DSA : undefined ,
dependencies . otr ? dependencies . otr . OTR : undefined ,
dependencies . Strophe ,
dependencies . underscore ,
dependencies . moment ,
dependencies . utils ,
dependencies . SHA1 . b64 _sha1
) ;
2013-11-16 10:24:22 +01:00
}
2013-12-30 20:27:57 +01:00
) ;
2013-02-20 17:21:07 +01:00
} else {
2015-07-10 10:35:33 +02:00
// When not using a module loader
// -------------------------------
// In this case, the dependencies need to be available already as
// global variables, and should be loaded separately via *script* tags.
// See the file **non_amd.html** for an example of this usecase.
2015-04-06 12:26:08 +02:00
root . converse = factory ( templates , jQuery , $iq , $msg , $pres , $build , DSA , OTR , Strophe , _ , moment , utils , b64 _sha1 ) ;
2012-09-21 16:04:57 +02:00
}
2015-02-03 22:39:03 +01:00
} ( this , function ( templates , $ , $iq , $msg , $pres , $build , DSA , OTR , Strophe , _ , moment , utils , b64 _sha1 ) {
2015-07-10 10:35:33 +02:00
/ * " u s e s t r i c t " ;
* Cannot use this due to Safari bug .
* See https : //github.com/jcbrand/converse.js/issues/196
* /
2014-09-20 15:07:55 +02:00
2014-09-17 23:05:17 +02:00
// Use Mustache style syntax for variable interpolation
2015-07-10 10:35:33 +02:00
/ * C o n f i g u r a t i o n o f u n d e r s c o r e t e m p l a t e s ( t h i s c o n f i g i s d i s t i n c t t o t h e
* config of requirejs - tpl in main . js ) . This one is for normal inline templates .
* /
2014-09-17 23:05:17 +02:00
_ . templateSettings = {
evaluate : /\{\[([\s\S]+?)\]\}/g ,
interpolate : /\{\{([\s\S]+?)\}\}/g
} ;
2014-01-22 23:53:50 +01:00
2014-08-30 13:26:10 +02:00
var contains = function ( attr , query ) {
2014-08-27 18:57:03 +02:00
return function ( item ) {
2014-08-31 19:25:54 +02:00
if ( typeof attr === 'object' ) {
var value = false ;
_ . each ( attr , function ( a ) {
value = value || item . get ( a ) . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) !== - 1 ;
} ) ;
return value ;
} else if ( typeof attr === 'string' ) {
return item . get ( attr ) . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) !== - 1 ;
} else {
2015-03-31 15:26:57 +02:00
throw new TypeError ( 'contains: wrong attribute type. Must be string or array.' ) ;
2014-08-31 19:25:54 +02:00
}
2014-08-30 13:26:10 +02:00
} ;
} ;
contains . not = function ( attr , query ) {
return function ( item ) {
2014-08-31 19:25:54 +02:00
return ! ( contains ( attr , query ) ( item ) ) ;
2014-08-27 18:57:03 +02:00
} ;
} ;
2013-12-16 13:37:30 +01:00
var converse = {
2014-10-12 14:49:45 +02:00
plugins : { } ,
2013-12-30 20:27:57 +01:00
templates : templates ,
2014-10-10 11:05:16 +02:00
emit : function ( evt , data ) {
2013-12-16 13:37:30 +01:00
$ ( this ) . trigger ( evt , data ) ;
} ,
2014-10-10 11:05:16 +02:00
once : function ( evt , handler ) {
2013-12-16 13:37:30 +01:00
$ ( this ) . one ( evt , handler ) ;
} ,
2014-10-10 11:05:16 +02:00
on : function ( evt , handler ) {
2013-12-16 13:37:30 +01:00
$ ( this ) . bind ( evt , handler ) ;
} ,
2014-10-10 11:05:16 +02:00
off : function ( evt , handler ) {
2013-12-16 13:37:30 +01:00
$ ( this ) . unbind ( evt , handler ) ;
2014-03-01 00:51:07 +01:00
} ,
refreshWebkit : function ( ) {
/ * T h i s w o r k s a r o u n d a w e b k i t b u g . R e f r e s h t h e b r o w s e r ' s v i e w p o r t ,
2015-09-22 14:39:52 +02:00
* otherwise chatboxes are not moved along when one is closed .
* /
2014-03-01 00:51:07 +01:00
if ( $ . browser . webkit ) {
var conversejs = document . getElementById ( 'conversejs' ) ;
conversejs . style . display = 'none' ;
2014-08-04 19:38:48 +02:00
conversejs . offsetHeight = conversejs . offsetHeight ;
2014-03-01 00:51:07 +01:00
conversejs . style . display = 'block' ;
}
2014-02-23 04:49:30 +01:00
}
2014-01-22 22:19:45 +01:00
} ;
2015-07-11 12:03:20 +02:00
// Global constants
// XEP-0059 Result Set Management
var RSM _ATTRIBUTES = [ 'max' , 'first' , 'last' , 'after' , 'before' , 'index' , 'count' ] ;
2015-07-11 14:55:02 +02:00
// XEP-0313 Message Archive Management
var MAM _ATTRIBUTES = [ 'with' , 'start' , 'end' ] ;
2015-07-11 12:03:20 +02:00
2015-06-27 21:21:27 +02:00
var STATUS _WEIGHTS = {
'offline' : 6 ,
'unavailable' : 5 ,
'xa' : 4 ,
'away' : 3 ,
'dnd' : 2 ,
'chat' : 1 , // We currently don't differentiate between "chat" and "online"
'online' : 1
} ;
2013-08-24 03:10:06 +02:00
converse . initialize = function ( settings , callback ) {
2015-06-19 17:58:03 +02:00
"use strict" ;
2013-10-03 14:24:23 +02:00
var converse = this ;
2015-06-05 14:45:55 +02:00
var unloadevent ;
2015-06-19 17:58:03 +02:00
if ( 'onpagehide' in window ) {
// Pagehide gets thrown in more cases than unload. Specifically it
// gets thrown when the page is cached and not just
// closed/destroyed. It's the only viable event on mobile Safari.
// https://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
unloadevent = 'pagehide' ;
} else if ( 'onbeforeunload' in window ) {
2015-06-05 14:45:55 +02:00
unloadevent = 'beforeunload' ;
} else if ( 'onunload' in window ) {
unloadevent = 'unload' ;
}
2013-10-03 14:24:23 +02:00
2014-11-19 21:20:36 +01:00
// Logging
2015-01-22 21:38:36 +01:00
Strophe . log = function ( level , msg ) { converse . log ( level + ' ' + msg , level ) ; } ;
Strophe . error = function ( msg ) { converse . log ( msg , 'error' ) ; } ;
2014-11-19 21:20:36 +01:00
2014-11-18 11:23:50 +01:00
// Add Strophe Namespaces
2015-07-10 15:07:32 +02:00
Strophe . addNamespace ( 'CARBONS' , 'urn:xmpp:carbons:2' ) ;
2015-01-01 21:25:12 +01:00
Strophe . addNamespace ( 'CHATSTATES' , 'http://jabber.org/protocol/chatstates' ) ;
2015-07-10 15:07:32 +02:00
Strophe . addNamespace ( 'CSI' , 'urn:xmpp:csi:0' ) ;
Strophe . addNamespace ( 'MAM' , 'urn:xmpp:mam:0' ) ;
2015-03-04 23:22:19 +01:00
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" ) ;
2014-11-18 11:23:50 +01:00
Strophe . addNamespace ( 'REGISTER' , 'jabber:iq:register' ) ;
2015-04-15 22:06:23 +02:00
Strophe . addNamespace ( 'ROSTERX' , 'http://jabber.org/protocol/rosterx' ) ;
2015-07-10 17:26:54 +02:00
Strophe . addNamespace ( 'RSM' , 'http://jabber.org/protocol/rsm' ) ;
2014-11-18 11:23:50 +01:00
Strophe . addNamespace ( 'XFORM' , 'jabber:x:data' ) ;
// Add Strophe Statuses
var i = 0 ;
Object . keys ( Strophe . Status ) . forEach ( function ( key ) {
i = Math . max ( i , Strophe . Status [ key ] ) ;
} ) ;
Strophe . Status . REGIFAIL = i + 1 ;
Strophe . Status . REGISTERED = i + 2 ;
Strophe . Status . CONFLICT = i + 3 ;
Strophe . Status . NOTACCEPTABLE = i + 5 ;
2013-09-08 15:54:04 +02:00
// Constants
2013-10-05 22:38:14 +02:00
// ---------
2015-04-02 13:07:59 +02:00
var LOGIN = "login" ;
2015-03-22 12:14:45 +01:00
var ANONYMOUS = "anonymous" ;
var PREBIND = "prebind" ;
2013-09-08 15:54:04 +02:00
var UNENCRYPTED = 0 ;
var UNVERIFIED = 1 ;
var VERIFIED = 2 ;
var FINISHED = 3 ;
2013-09-18 09:25:40 +02:00
var KEY = {
2015-01-01 21:25:12 +01:00
ENTER : 13 ,
FORWARD _SLASH : 47
2013-09-18 09:25:40 +02:00
} ;
2015-01-01 21:25:12 +01:00
2015-06-07 18:22:30 +02:00
var PRETTY _CONNECTION _STATUS = {
0 : 'ERROR' ,
1 : 'CONNECTING' ,
2 : 'CONNFAIL' ,
3 : 'AUTHENTICATING' ,
4 : 'AUTHFAIL' ,
5 : 'CONNECTED' ,
6 : 'DISCONNECTED' ,
7 : 'DISCONNECTING' ,
8 : 'ATTACHED' ,
9 : 'REDIRECT'
} ;
2015-01-01 21:25:12 +01:00
// XEP-0085 Chat states
// http://xmpp.org/extensions/xep-0085.html
2014-08-20 21:00:28 +02:00
var INACTIVE = 'inactive' ;
var ACTIVE = 'active' ;
var COMPOSING = 'composing' ;
var PAUSED = 'paused' ;
2014-09-22 16:35:36 +02:00
var GONE = 'gone' ;
2015-01-01 21:25:12 +01:00
this . TIMEOUTS = { // Set as module attr so that we can override in tests.
2015-01-09 09:02:35 +01:00
'PAUSED' : 20000 ,
2015-01-09 10:48:36 +01:00
'INACTIVE' : 90000
2015-01-01 21:25:12 +01:00
} ;
2013-11-16 10:52:45 +01:00
var HAS _CSPRNG = ( ( typeof crypto !== 'undefined' ) &&
( ( typeof crypto . randomBytes === 'function' ) ||
( typeof crypto . getRandomValues === 'function' )
2013-11-15 22:33:05 +01:00
) ) ;
2013-11-16 10:24:22 +01:00
var HAS _CRYPTO = HAS _CSPRNG && (
( typeof CryptoJS !== "undefined" ) &&
( typeof OTR !== "undefined" ) &&
( typeof DSA !== "undefined" )
) ;
2014-08-02 14:25:24 +02:00
var OPENED = 'opened' ;
var CLOSED = 'closed' ;
2015-06-22 19:41:37 +02:00
// Detect support for the user's locale
// ------------------------------------
this . isConverseLocale = function ( locale ) { return typeof locales [ locale ] !== "undefined" ; } ;
2015-10-25 18:49:35 +01:00
this . isMomentLocale = function ( locale ) { return moment . locale ( ) !== moment . locale ( locale ) ; } ;
2015-05-27 14:29:22 +02:00
2015-06-22 19:41:37 +02:00
this . isLocaleAvailable = function ( locale , available ) {
/ * C h e c k w h e t h e r t h e l o c a l e o r s u b l o c a l e ( e . g . e n - U S , e n ) i s s u p p o r t e d .
*
* Parameters :
* ( Function ) available - returns a boolean indicating whether the locale is supported
* /
2015-05-27 14:29:22 +02:00
if ( available ( locale ) ) {
2015-06-22 19:41:37 +02:00
return locale ;
2015-06-19 17:58:03 +02:00
} else {
var sublocale = locale . split ( "-" ) [ 0 ] ;
2015-10-25 18:49:35 +01:00
if ( sublocale !== locale && available ( sublocale ) ) {
2015-06-22 19:41:37 +02:00
return sublocale ;
2015-05-20 19:31:59 +02:00
}
2015-05-11 17:36:12 +02:00
}
} ;
2015-06-22 19:41:37 +02:00
this . detectLocale = function ( library _check ) {
/ * D e t e r m i n e w h i c h l o c a l e i s s u p p o r t e d b y t h e u s e r ' s s y s t e m a s w e l l
* as by the relevant library ( e . g . converse . js or moment . js ) .
*
* Parameters :
* ( Function ) library _check - returns a boolean indicating whether the locale is supported
* /
var locale , i ;
2015-05-20 19:31:59 +02:00
if ( window . navigator . userLanguage ) {
2015-06-22 19:41:37 +02:00
locale = this . isLocaleAvailable ( window . navigator . userLanguage , library _check ) ;
}
if ( window . navigator . languages && ! locale ) {
for ( i = 0 ; i < window . navigator . languages . length && ! locale ; i ++ ) {
locale = this . isLocaleAvailable ( window . navigator . languages [ i ] , library _check ) ;
2015-05-11 17:36:12 +02:00
}
}
2015-06-22 19:41:37 +02:00
if ( window . navigator . browserLanguage && ! locale ) {
locale = this . isLocaleAvailable ( window . navigator . browserLanguage , library _check ) ;
}
if ( window . navigator . language && ! locale ) {
locale = this . isLocaleAvailable ( window . navigator . language , library _check ) ;
}
if ( window . navigator . systemLanguage && ! locale ) {
locale = this . isLocaleAvailable ( window . navigator . systemLanguage , library _check ) ;
}
2015-06-22 20:31:51 +02:00
return locale || 'en' ;
2015-05-11 17:36:12 +02:00
} ;
2015-05-27 14:29:22 +02:00
2015-06-22 19:41:37 +02:00
if ( ! moment . locale ) { //moment.lang is deprecated after 2.8.1, use moment.locale instead
moment . locale = moment . lang ;
}
moment . locale ( this . detectLocale ( this . isMomentLocale ) ) ;
this . i18n = settings . i18n ? settings . i18n : locales [ this . detectLocale ( this . isConverseLocale ) ] ;
2015-05-20 19:31:59 +02:00
2015-06-22 19:41:37 +02:00
// Translation machinery
// ---------------------
2015-06-27 06:36:25 +02:00
var _ _ = utils . _ _ . bind ( this ) ;
2014-12-31 11:28:52 +01:00
var _ _ _ = utils . _ _ _ ;
2013-10-05 22:34:47 +02:00
// Default configuration values
// ----------------------------
2015-02-11 15:02:04 +01:00
this . default _settings = {
2015-06-18 16:31:30 +02:00
allow _chat _pending _contacts : false ,
2015-03-09 19:47:01 +01:00
allow _contact _removal : true ,
2014-11-17 09:50:00 +01:00
allow _contact _requests : true ,
allow _dragresize : true ,
allow _logout : true ,
allow _muc : true ,
allow _otr : true ,
2015-07-22 00:18:30 +02:00
archived _messages _page _size : '20' ,
2015-06-22 00:01:31 +02:00
auto _away : 0 , // Seconds after which user status is set to 'away'
auto _xa : 0 , // Seconds after which user status is set to 'xa'
2014-11-17 09:50:00 +01:00
allow _registration : true ,
animate : true ,
auto _list _rooms : false ,
2015-03-22 13:20:24 +01:00
auto _login : false , // Currently only used in connection with anonymous login
2014-11-17 09:50:00 +01:00
auto _reconnect : false ,
auto _subscribe : false ,
bosh _service _url : undefined , // The BOSH connection manager URL.
cache _otr _key : false ,
2015-06-22 00:01:31 +02:00
csi _waiting _time : 0 , // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
2014-11-17 09:50:00 +01:00
debug : false ,
2014-12-20 11:57:03 +01:00
domain _placeholder : _ _ ( " e.g. conversejs.org" ) , // Placeholder text shown in the domain input on the registration form
2014-11-17 09:50:00 +01:00
expose _rid _and _sid : false ,
forward _messages : false ,
hide _muc _server : false ,
hide _offline _users : false ,
2014-12-06 19:05:27 +01:00
jid : undefined ,
2014-11-17 09:50:00 +01:00
keepalive : false ,
2015-07-10 15:07:53 +02:00
message _archiving : 'never' , // Supported values are 'always', 'never', 'roster' (See https://xmpp.org/extensions/xep-0313.html#prefs )
2015-06-22 00:01:31 +02:00
message _carbons : false , // Support for XEP-280
2015-07-21 11:35:39 +02:00
muc _history _max _stanzas : undefined , // Takes an integer, limits the amount of messages to fetch from chat room's history
2014-11-17 09:50:00 +01:00
no _trimming : false , // Set to true for phantomjs tests (where browser apparently has no width)
2015-05-27 18:14:58 +02:00
ping _interval : 180 , //in seconds
2014-11-17 09:50:00 +01:00
play _sounds : false ,
2015-04-25 22:40:55 +02:00
sounds _path : '/sounds/' ,
2015-04-02 13:07:59 +02:00
password : undefined ,
authentication : 'login' , // Available values are "login", "prebind", "anonymous".
2015-03-22 12:14:45 +01:00
prebind : false , // XXX: Deprecated, use "authentication" instead.
2015-03-05 22:24:26 +01:00
prebind _url : null ,
2014-12-06 17:29:28 +01:00
providers _link : 'https://xmpp.net/directory.php' , // Link to XMPP providers shown on registration page
2014-12-06 19:05:27 +01:00
rid : undefined ,
2014-11-17 09:50:00 +01:00
roster _groups : false ,
show _controlbox _by _default : false ,
show _only _online _users : false ,
show _toolbar : true ,
2014-12-06 19:05:27 +01:00
sid : undefined ,
2014-11-17 09:50:00 +01:00
storage : 'session' ,
use _otr _by _default : false ,
use _vcards : true ,
visible _toolbar _buttons : {
'emoticons' : true ,
'call' : false ,
'clear' : true ,
2015-10-31 17:30:06 +01:00
'toggle_occupants' : true
2014-11-17 09:50:00 +01:00
} ,
2015-02-07 16:54:58 +01:00
websocket _url : undefined ,
2014-11-17 09:50:00 +01:00
xhr _custom _status : false ,
xhr _custom _status _url : '' ,
xhr _user _search : false ,
xhr _user _search _url : ''
2014-04-24 18:03:30 +02:00
} ;
2015-06-22 00:01:31 +02:00
2015-02-11 15:02:04 +01:00
_ . extend ( this , this . default _settings ) ;
2013-10-05 22:34:47 +02:00
// Allow only whitelisted configuration attributes to be overwritten
2015-02-11 15:02:04 +01:00
_ . extend ( this , _ . pick ( settings , Object . keys ( this . default _settings ) ) ) ;
2014-11-17 09:50:00 +01:00
2015-03-22 12:14:45 +01:00
// BBB
if ( this . prebind === true ) { this . authentication = PREBIND ; }
2015-03-22 13:20:24 +01:00
if ( this . authentication === ANONYMOUS ) {
if ( ! this . jid ) {
throw ( "Config Error: you need to provide the server's domain via the " +
"'jid' option when using anonymous authentication." ) ;
}
}
2014-04-24 19:14:37 +02:00
if ( settings . visible _toolbar _buttons ) {
_ . extend (
this . visible _toolbar _buttons ,
_ . pick ( settings . visible _toolbar _buttons , [
2015-10-31 17:30:06 +01:00
'emoticons' , 'call' , 'clear' , 'toggle_occupants'
2014-04-24 19:14:37 +02:00
]
) ) ;
}
2014-04-24 07:58:35 +02:00
$ . fx . off = ! this . animate ;
2013-02-21 13:42:30 +01:00
2013-11-15 21:16:18 +01:00
// Only allow OTR if we have the capability
2013-11-16 10:24:22 +01:00
this . allow _otr = this . allow _otr && HAS _CRYPTO ;
2013-11-15 21:16:18 +01:00
2013-12-10 11:28:22 +01:00
// Only use OTR by default if allow OTR is enabled to begin with
this . use _otr _by _default = this . use _otr _by _default && this . allow _otr ;
2013-10-05 22:34:47 +02:00
// Translation aware constants
// ---------------------------
2013-09-08 15:54:04 +02:00
var OTR _CLASS _MAPPING = { } ;
OTR _CLASS _MAPPING [ UNENCRYPTED ] = 'unencrypted' ;
OTR _CLASS _MAPPING [ UNVERIFIED ] = 'unverified' ;
OTR _CLASS _MAPPING [ VERIFIED ] = 'verified' ;
OTR _CLASS _MAPPING [ FINISHED ] = 'finished' ;
var OTR _TRANSLATED _MAPPING = { } ;
OTR _TRANSLATED _MAPPING [ UNENCRYPTED ] = _ _ ( 'unencrypted' ) ;
OTR _TRANSLATED _MAPPING [ UNVERIFIED ] = _ _ ( 'unverified' ) ;
OTR _TRANSLATED _MAPPING [ VERIFIED ] = _ _ ( 'verified' ) ;
OTR _TRANSLATED _MAPPING [ FINISHED ] = _ _ ( 'finished' ) ;
2013-08-26 16:21:32 +02:00
2013-10-05 22:34:47 +02:00
var STATUSES = {
'dnd' : _ _ ( 'This contact is busy' ) ,
'online' : _ _ ( 'This contact is online' ) ,
'offline' : _ _ ( 'This contact is offline' ) ,
'unavailable' : _ _ ( 'This contact is unavailable' ) ,
'xa' : _ _ ( 'This contact is away for an extended period' ) ,
'away' : _ _ ( 'This contact is away' )
} ;
2014-07-29 19:53:57 +02:00
var DESC _GROUP _TOGGLE = _ _ ( 'Click to hide these contacts' ) ;
2014-08-02 14:25:24 +02:00
2014-07-29 19:53:57 +02:00
var HEADER _CURRENT _CONTACTS = _ _ ( 'My contacts' ) ;
2014-08-02 14:25:24 +02:00
var HEADER _PENDING _CONTACTS = _ _ ( 'Pending contacts' ) ;
var HEADER _REQUESTING _CONTACTS = _ _ ( 'Contact requests' ) ;
2014-07-29 19:53:57 +02:00
var HEADER _UNGROUPED = _ _ ( 'Ungrouped' ) ;
2013-10-05 22:34:47 +02:00
2014-08-11 21:47:51 +02:00
var LABEL _CONTACTS = _ _ ( 'Contacts' ) ;
var LABEL _GROUPS = _ _ ( 'Groups' ) ;
2014-08-02 14:25:24 +02:00
var HEADER _WEIGHTS = { } ;
HEADER _WEIGHTS [ HEADER _CURRENT _CONTACTS ] = 0 ;
HEADER _WEIGHTS [ HEADER _UNGROUPED ] = 1 ;
HEADER _WEIGHTS [ HEADER _REQUESTING _CONTACTS ] = 2 ;
HEADER _WEIGHTS [ HEADER _PENDING _CONTACTS ] = 3 ;
2013-10-05 22:34:47 +02:00
// Module-level variables
// ----------------------
this . callback = callback || function ( ) { } ;
2013-10-15 18:29:16 +02:00
this . initial _presence _sent = 0 ;
2013-06-02 00:21:06 +02:00
this . msg _counter = 0 ;
2013-10-05 22:34:47 +02:00
// Module-level functions
// ----------------------
2015-05-27 20:58:02 +02:00
this . sendCSI = function ( stat ) {
2015-06-22 00:01:31 +02:00
/* Send out a Chat Status Notification (XEP-0352) */
if ( converse . features [ Strophe . NS . CSI ] || true ) {
2015-05-27 20:58:02 +02:00
converse . connection . send ( $build ( stat , { xmlns : Strophe . NS . CSI } ) ) ;
2015-06-22 00:01:31 +02:00
this . inactive = ( stat === INACTIVE ) ? true : false ;
2015-05-27 20:58:02 +02:00
}
} ;
2015-06-22 00:01:31 +02:00
this . onUserActivity = function ( ) {
2015-06-22 18:39:12 +02:00
/* Resets counters and flags relating to CSI and auto_away/auto_xa */
2015-06-22 00:01:31 +02:00
if ( this . idle _seconds > 0 ) {
this . idle _seconds = 0 ;
}
2015-06-22 18:39:12 +02:00
if ( ! converse . connection . authenticated ) {
// We can't send out any stanzas when there's no authenticated connection.
// This can happen when the connection reconnects.
return ;
}
2015-06-22 00:01:31 +02:00
if ( this . inactive ) {
this . sendCSI ( ACTIVE ) ;
}
if ( this . auto _changed _status === true ) {
this . auto _changed _status = false ;
this . xmppstatus . setStatus ( 'online' ) ;
2015-05-13 13:51:24 +02:00
}
} ;
2015-06-22 00:01:31 +02:00
this . onEverySecond = function ( ) {
2015-06-22 18:39:12 +02:00
/ * A n i n t e r v a l h a n d l e r r u n n i n g e v e r y s e c o n d .
* Used for CSI and the auto _away and auto _xa
* features .
* /
if ( ! converse . connection . authenticated ) {
// We can't send out any stanzas when there's no authenticated connection.
// This can happen when the connection reconnects.
return ;
}
2015-06-22 00:01:31 +02:00
var stat = this . xmppstatus . getStatus ( ) ;
this . idle _seconds ++ ;
2015-07-02 11:23:19 +02:00
if ( this . csi _waiting _time > 0 && this . idle _seconds > this . csi _waiting _time && ! this . inactive ) {
2015-06-22 00:01:31 +02:00
this . sendCSI ( INACTIVE ) ;
}
if ( this . auto _away > 0 && this . idle _seconds > this . auto _away && stat !== 'away' && stat !== 'xa' ) {
this . auto _changed _status = true ;
this . xmppstatus . setStatus ( 'away' ) ;
} else if ( this . auto _xa > 0 && this . idle _seconds > this . auto _xa && stat !== 'xa' ) {
this . auto _changed _status = true ;
this . xmppstatus . setStatus ( 'xa' ) ;
2015-05-13 13:51:24 +02:00
}
} ;
2015-06-22 00:01:31 +02:00
this . registerIntervalHandler = function ( ) {
/ * S e t a n i n t e r v a l o f o n e s e c o n d a n d r e g i s t e r a h a n d l e r f o r i t .
* Required for the auto _away , auto _xa and csi _waiting _time features .
* /
if ( this . auto _away < 1 && this . auto _xa < 1 && this . csi _waiting _time < 1 ) {
// Waiting time of less then one second means features aren't used.
return ;
}
this . idle _seconds = 0 ;
this . auto _changed _status = false ; // Was the user's status changed by converse.js?
$ ( window ) . on ( 'click mousemove keypress focus' + unloadevent , this . onUserActivity . bind ( this ) ) ;
window . setInterval ( this . onEverySecond . bind ( this ) , 1000 ) ;
} ;
2015-05-13 13:51:24 +02:00
2015-03-16 17:27:26 +01:00
this . playNotification = function ( ) {
var audio ;
2015-05-27 20:58:02 +02:00
if ( converse . play _sounds && typeof Audio !== "undefined" ) {
2015-04-25 22:40:55 +02:00
audio = new Audio ( converse . sounds _path + "msg_received.ogg" ) ;
2015-03-16 17:27:26 +01:00
if ( audio . canPlayType ( '/audio/ogg' ) ) {
audio . play ( ) ;
} else {
2015-04-25 22:40:55 +02:00
audio = new Audio ( converse . sounds _path + "msg_received.mp3" ) ;
2015-03-16 17:27:26 +01:00
audio . play ( ) ;
}
2015-04-08 13:41:31 +02:00
}
2015-03-16 17:27:26 +01:00
} ;
2013-08-23 00:48:53 +02:00
this . giveFeedback = function ( message , klass ) {
2014-11-24 22:40:10 +01:00
$ ( '.conn-feedback' ) . each ( function ( idx , el ) {
var $el = $ ( el ) ;
$el . addClass ( 'conn-feedback' ) . text ( message ) ;
if ( klass ) {
$el . addClass ( klass ) ;
} else {
$el . removeClass ( 'error' ) ;
}
} ) ;
2013-08-23 00:48:53 +02:00
} ;
2013-09-08 15:54:04 +02:00
this . log = function ( txt , level ) {
2015-10-25 18:49:35 +01:00
var logger ;
if ( typeof console === "undefined" || typeof console . log === "undefined" ) {
logger = { log : function ( ) { } , error : function ( ) { } } ;
} else {
logger = console ;
}
2013-08-22 21:43:34 +02:00
if ( this . debug ) {
2015-10-25 18:49:35 +01:00
if ( level === 'error' ) {
logger . log ( 'ERROR: ' + txt ) ;
2013-09-08 15:54:04 +02:00
} else {
2015-10-25 18:49:35 +01:00
logger . log ( txt ) ;
2013-09-08 15:54:04 +02:00
}
2013-08-22 21:43:34 +02:00
}
} ;
2015-04-06 13:41:48 +02:00
2015-04-06 12:26:08 +02:00
this . rejectPresenceSubscription = function ( jid , message ) {
/ * R e j e c t o r c a n c e l a n o t h e r u s e r ' s s u b s c r i p t i o n t o o u r p r e s e n c e u p d a t e s .
* Parameters :
* ( String ) jid - The Jabber ID of the user whose subscription
* is being canceled .
2015-04-06 13:41:48 +02:00
* ( String ) message - An optional message to the user
2015-04-06 12:26:08 +02:00
* /
var pres = $pres ( { to : jid , type : "unsubscribed" } ) ;
if ( message && message !== "" ) { pres . c ( "status" ) . t ( message ) ; }
converse . connection . send ( pres ) ;
} ;
2013-08-22 21:43:34 +02:00
2013-10-05 22:34:47 +02:00
this . getVCard = function ( jid , callback , errback ) {
2015-04-06 12:26:08 +02:00
/ * R e q u e s t t h e V C a r d o f a n o t h e r u s e r .
2015-07-11 13:40:02 +02:00
*
* Parameters :
2015-04-06 12:26:08 +02:00
* ( String ) jid - The Jabber ID of the user whose VCard is being requested .
* ( Function ) callback - A function to call once the VCard is returned
* ( Function ) errback - A function to call if an error occured
* while trying to fetch the VCard .
* /
2013-12-17 18:24:36 +01:00
if ( ! this . use _vcards ) {
2015-04-06 13:41:48 +02:00
if ( callback ) { callback ( jid , jid ) ; }
2013-12-17 18:24:36 +01:00
return ;
}
2013-10-05 22:34:47 +02:00
converse . connection . vcard . get (
2015-06-27 06:36:25 +02:00
function ( iq ) { // Successful callback
2013-11-11 20:56:11 +01:00
var $vcard = $ ( iq ) . find ( 'vCard' ) ;
2013-10-05 22:34:47 +02:00
var fullname = $vcard . find ( 'FN' ) . text ( ) ,
img = $vcard . find ( 'BINVAL' ) . text ( ) ,
img _type = $vcard . find ( 'TYPE' ) . text ( ) ,
url = $vcard . find ( 'URL' ) . text ( ) ;
if ( jid ) {
2014-08-03 23:02:25 +02:00
var contact = converse . roster . get ( jid ) ;
2014-08-02 11:35:03 +02:00
if ( contact ) {
fullname = _ . isEmpty ( fullname ) ? contact . get ( 'fullname' ) || jid : fullname ;
contact . save ( {
2013-11-11 20:56:11 +01:00
'fullname' : fullname ,
2013-10-05 22:34:47 +02:00
'image_type' : img _type ,
'image' : img ,
'url' : url ,
2014-04-25 22:56:59 +02:00
'vcard_updated' : moment ( ) . format ( )
2013-10-05 22:34:47 +02:00
} ) ;
}
}
2015-04-06 13:41:48 +02:00
if ( callback ) { callback ( iq , jid , fullname , img , img _type , url ) ; }
2015-06-27 06:36:25 +02:00
} . bind ( this ) ,
2013-10-05 22:34:47 +02:00
jid ,
2015-04-06 13:41:48 +02:00
function ( iq ) { // Error callback
2014-08-03 23:02:25 +02:00
var contact = converse . roster . get ( jid ) ;
2014-08-02 11:35:03 +02:00
if ( contact ) {
2015-04-06 13:41:48 +02:00
contact . save ( { 'vcard_updated' : moment ( ) . format ( ) } ) ;
2013-10-05 22:34:47 +02:00
}
2015-04-06 13:41:48 +02:00
if ( errback ) { errback ( iq , jid ) ; }
2013-12-17 18:24:36 +01:00
}
) ;
2013-10-05 22:34:47 +02:00
} ;
2015-06-07 18:26:32 +02:00
this . reconnect = function ( condition ) {
converse . log ( 'Attempting to reconnect in 5 seconds' ) ;
converse . giveFeedback ( _ _ ( 'Attempting to reconnect in 5 seconds' ) , 'error' ) ;
setTimeout ( function ( ) {
if ( converse . authentication !== "prebind" ) {
this . connection . connect (
this . connection . jid ,
this . connection . pass ,
function ( status , condition ) {
this . onConnectStatusChanged ( status , condition , true ) ;
} . bind ( this ) ,
this . connection . wait ,
this . connection . hold ,
this . connection . route
) ;
} else if ( converse . prebind _url ) {
this . clearSession ( ) ;
this . _tearDown ( ) ;
this . startNewBOSHSession ( ) ;
}
} . bind ( this ) , 5000 ) ;
2014-02-11 12:14:36 +01:00
} ;
2014-09-18 18:51:23 +02:00
this . renderLoginPanel = function ( ) {
2014-09-18 09:29:31 +02:00
converse . _tearDown ( ) ;
2014-03-04 14:54:36 +01:00
var view = converse . chatboxviews . get ( 'controlbox' ) ;
2014-09-07 13:18:36 +02:00
view . model . set ( { connected : false } ) ;
2014-09-18 18:51:23 +02:00
view . renderLoginPanel ( ) ;
2014-02-11 12:14:36 +01:00
} ;
2015-06-07 18:22:30 +02:00
this . onConnectStatusChanged = function ( status , condition , reconnect ) {
converse . log ( "Status changed to: " + PRETTY _CONNECTION _STATUS [ status ] ) ;
2015-04-06 20:55:38 +02:00
if ( status === Strophe . Status . CONNECTED || status === Strophe . Status . ATTACHED ) {
2015-06-07 18:29:36 +02:00
delete converse . disconnection _cause ;
2014-02-11 12:44:27 +01:00
if ( ( typeof reconnect !== 'undefined' ) && ( reconnect ) ) {
converse . log ( status === Strophe . Status . CONNECTED ? 'Reconnected' : 'Reattached' ) ;
converse . onReconnected ( ) ;
} else {
converse . log ( status === Strophe . Status . CONNECTED ? 'Connected' : 'Attached' ) ;
converse . onConnected ( ) ;
}
2013-08-23 00:48:53 +02:00
} else if ( status === Strophe . Status . DISCONNECTED ) {
2015-10-25 18:49:35 +01:00
if ( converse . disconnection _cause === Strophe . Status . CONNFAIL && converse . auto _reconnect ) {
2015-06-07 18:29:36 +02:00
converse . reconnect ( condition ) ;
2014-02-11 12:14:36 +01:00
} else {
2014-09-18 18:51:23 +02:00
converse . renderLoginPanel ( ) ;
2014-02-11 12:14:36 +01:00
}
2015-06-07 18:22:30 +02:00
} else if ( status === Strophe . Status . ERROR ) {
2013-08-23 00:48:53 +02:00
converse . giveFeedback ( _ _ ( 'Error' ) , 'error' ) ;
} else if ( status === Strophe . Status . CONNECTING ) {
converse . giveFeedback ( _ _ ( 'Connecting' ) ) ;
} else if ( status === Strophe . Status . AUTHENTICATING ) {
converse . giveFeedback ( _ _ ( 'Authenticating' ) ) ;
} else if ( status === Strophe . Status . AUTHFAIL ) {
converse . giveFeedback ( _ _ ( 'Authentication Failed' ) , 'error' ) ;
2014-11-24 22:40:10 +01:00
converse . connection . disconnect ( _ _ ( 'Authentication Failed' ) ) ;
2015-06-07 18:29:36 +02:00
converse . disconnection _cause = Strophe . Status . AUTHFAIL ;
} else if ( status === Strophe . Status . CONNFAIL ) {
converse . disconnection _cause = Strophe . Status . CONNFAIL ;
2013-08-23 00:48:53 +02:00
} else if ( status === Strophe . Status . DISCONNECTING ) {
2015-03-21 23:41:39 +01:00
// FIXME: what about prebind?
2014-09-18 18:51:23 +02:00
if ( ! converse . connection . connected ) {
converse . renderLoginPanel ( ) ;
2014-11-24 22:40:10 +01:00
}
if ( condition ) {
converse . giveFeedback ( condition , 'error' ) ;
2014-09-18 18:51:23 +02:00
}
2013-08-23 00:48:53 +02:00
}
} ;
2015-09-24 19:41:35 +02:00
this . applyDragResistance = function ( value , default _value ) {
/ * T h i s m e t h o d a p p l i e s s o m e r e s i s t a n c e a r o u n d t h e
* default _value . If value is close enough to
* default _value , then default _value is returned instead .
2014-03-01 00:51:07 +01:00
* /
2015-09-24 19:41:35 +02:00
if ( typeof value === 'undefined' ) {
return undefined ;
} else if ( typeof default _value === 'undefined' ) {
return value ;
2014-03-01 00:51:07 +01:00
}
var resistance = 10 ;
2015-09-24 19:41:35 +02:00
if ( ( value !== default _value ) &&
( Math . abs ( value - default _value ) < resistance ) ) {
return default _value ;
2014-03-01 00:51:07 +01:00
}
2015-09-24 19:41:35 +02:00
return value ;
2014-03-01 00:51:07 +01:00
} ;
2013-06-02 00:21:06 +02:00
this . updateMsgCounter = function ( ) {
if ( this . msg _counter > 0 ) {
2015-10-25 18:49:35 +01:00
if ( document . title . search ( /^Messages \(\d+\) / ) === - 1 ) {
2013-06-02 00:21:06 +02:00
document . title = "Messages (" + this . msg _counter + ") " + document . title ;
} else {
document . title = document . title . replace ( /^Messages \(\d+\) / , "Messages (" + this . msg _counter + ") " ) ;
}
window . blur ( ) ;
window . focus ( ) ;
2015-10-25 18:49:35 +01:00
} else if ( document . title . search ( /^Messages \(\d+\) / ) !== - 1 ) {
2013-06-02 00:21:06 +02:00
document . title = document . title . replace ( /^Messages \(\d+\) / , "" ) ;
}
} ;
2013-03-24 20:45:55 +01:00
2013-06-02 00:21:06 +02:00
this . incrementMsgCounter = function ( ) {
this . msg _counter += 1 ;
this . updateMsgCounter ( ) ;
} ;
2012-10-19 14:30:42 +02:00
2013-06-02 00:21:06 +02:00
this . clearMsgCounter = function ( ) {
this . msg _counter = 0 ;
this . updateMsgCounter ( ) ;
} ;
2013-10-05 22:34:47 +02:00
this . initStatus = function ( callback ) {
this . xmppstatus = new this . XMPPStatus ( ) ;
2014-06-30 18:53:58 +02:00
var id = b64 _sha1 ( 'converse.xmppstatus-' + converse . bare _jid ) ;
this . xmppstatus . id = id ; // Appears to be necessary for backbone.browserStorage
this . xmppstatus . browserStorage = new Backbone . BrowserStorage [ converse . storage ] ( id ) ;
2013-10-05 22:34:47 +02:00
this . xmppstatus . fetch ( { success : callback , error : callback } ) ;
2013-06-02 00:21:06 +02:00
} ;
2013-03-21 13:11:45 +01:00
2014-09-06 23:34:39 +02:00
this . initSession = function ( ) {
2015-06-19 18:01:42 +02:00
this . session = new this . Session ( ) ;
2014-09-06 23:34:39 +02:00
var id = b64 _sha1 ( 'converse.bosh-session' ) ;
this . session . id = id ; // Appears to be necessary for backbone.browserStorage
this . session . browserStorage = new Backbone . BrowserStorage [ converse . storage ] ( id ) ;
this . session . fetch ( ) ;
} ;
2014-09-07 13:18:36 +02:00
this . clearSession = function ( ) {
2015-05-07 17:47:32 +02:00
if ( this . roster ) {
this . roster . browserStorage . _clear ( ) ;
}
2014-09-07 13:18:36 +02:00
this . session . browserStorage . _clear ( ) ;
2015-05-07 17:47:32 +02:00
if ( converse . connection . connected ) {
converse . chatboxes . get ( 'controlbox' ) . save ( { 'connected' : false } ) ;
}
2014-09-07 13:18:36 +02:00
} ;
this . logOut = function ( ) {
converse . chatboxviews . closeAllChatBoxes ( false ) ;
converse . clearSession ( ) ;
converse . connection . disconnect ( ) ;
} ;
2014-01-31 04:50:38 +01:00
this . registerGlobalEventHandlers = function ( ) {
2014-10-10 11:05:16 +02:00
$ ( document ) . click ( function ( ) {
2014-01-31 04:50:38 +01:00
if ( $ ( '.toggle-otr ul' ) . is ( ':visible' ) ) {
$ ( '.toggle-otr ul' , this ) . slideUp ( ) ;
}
if ( $ ( '.toggle-smiley ul' ) . is ( ':visible' ) ) {
$ ( '.toggle-smiley ul' , this ) . slideUp ( ) ;
}
} ) ;
2015-06-27 06:36:25 +02:00
$ ( document ) . on ( 'mousemove' , function ( ev ) {
2015-09-24 19:41:35 +02:00
if ( ! this . resizing || ! this . allow _dragresize ) { return true ; }
2014-02-23 01:54:35 +01:00
ev . preventDefault ( ) ;
2015-09-24 19:41:35 +02:00
this . resizing . chatbox . resizeChatBox ( ev ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-02-23 01:54:35 +01:00
2015-06-27 06:36:25 +02:00
$ ( document ) . on ( 'mouseup' , function ( ev ) {
2015-09-24 19:41:35 +02:00
if ( ! this . resizing || ! this . allow _dragresize ) { return true ; }
2014-03-01 00:51:07 +01:00
ev . preventDefault ( ) ;
2015-09-24 19:41:35 +02:00
var height = this . applyDragResistance (
this . resizing . chatbox . height ,
this . resizing . chatbox . model . get ( 'default_height' )
) ;
var width = this . applyDragResistance (
this . resizing . chatbox . width ,
this . resizing . chatbox . model . get ( 'default_width' )
) ;
2014-09-06 23:34:39 +02:00
if ( this . connection . connected ) {
2015-09-24 19:41:35 +02:00
this . resizing . chatbox . model . save ( { 'height' : height } ) ;
this . resizing . chatbox . model . save ( { 'width' : width } ) ;
2014-02-28 13:22:15 +01:00
} else {
2015-09-24 19:41:35 +02:00
this . resizing . chatbox . model . set ( { 'height' : height } ) ;
this . resizing . chatbox . model . set ( { 'width' : width } ) ;
2014-02-28 13:22:15 +01:00
}
2015-09-24 19:41:35 +02:00
this . resizing = null ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-02-23 01:54:35 +01:00
2015-06-27 06:36:25 +02:00
$ ( window ) . on ( "blur focus" , function ( ev ) {
2015-10-25 18:49:35 +01:00
if ( ( this . windowState !== ev . type ) && ( ev . type === 'focus' ) ) {
2014-01-31 04:50:38 +01:00
converse . clearMsgCounter ( ) ;
}
2014-06-14 16:22:52 +02:00
this . windowState = ev . type ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-06-14 16:22:52 +02:00
2015-06-27 06:36:25 +02:00
$ ( window ) . on ( "resize" , _ . debounce ( function ( ev ) {
2014-06-14 16:22:52 +02:00
this . chatboxviews . trimChats ( ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) , 200 ) ) ;
2014-01-31 04:50:38 +01:00
} ;
2015-05-27 20:58:02 +02:00
this . ping = function ( jid , success , error , timeout ) {
// XXX: We could first check here if the server advertised that it supports PING.
// However, some servers don't advertise while still keeping the
// connection option due to pings.
//
// var feature = converse.features.findWhere({'var': Strophe.NS.PING});
2015-05-27 21:53:15 +02:00
converse . lastStanzaDate = new Date ( ) ;
if ( typeof jid === 'undefined' || jid === null ) {
jid = Strophe . getDomainFromJid ( converse . bare _jid ) ;
}
2015-05-27 18:14:58 +02:00
if ( typeof timeout === 'undefined' ) { timeout = null ; }
if ( typeof success === 'undefined' ) { success = null ; }
if ( typeof error === 'undefined' ) { error = null ; }
if ( converse . connection ) {
2015-05-27 20:58:02 +02:00
converse . connection . ping . ping ( jid , success , error , timeout ) ;
return true ;
}
2015-05-27 18:14:58 +02:00
return false ;
} ;
2015-05-27 20:58:02 +02:00
this . pong = function ( ping ) {
2015-05-27 21:53:15 +02:00
converse . lastStanzaDate = new Date ( ) ;
2015-05-27 18:14:58 +02:00
converse . connection . ping . pong ( ping ) ;
return true ;
} ;
2015-05-27 20:58:02 +02:00
this . registerPongHandler = function ( ) {
2015-05-27 21:53:15 +02:00
converse . connection . disco . addFeature ( Strophe . NS . PING ) ;
converse . connection . ping . addPingHandler ( this . pong ) ;
2015-05-27 18:14:58 +02:00
} ;
2015-05-27 20:58:02 +02:00
this . registerPingHandler = function ( ) {
2015-05-28 10:58:25 +02:00
this . registerPongHandler ( ) ;
2015-05-27 20:58:02 +02:00
if ( this . ping _interval > 0 ) {
2015-05-27 21:53:15 +02:00
this . connection . addHandler ( function ( ) {
/ * H a n d l e r o n e a c h s t a n z a , s a v e s t h e r e c e i v e d d a t e
* in order to ping only when needed .
* /
this . lastStanzaDate = new Date ( ) ;
return true ;
} . bind ( converse ) ) ;
this . connection . addTimedHandler ( 1000 , function ( ) {
2015-06-19 17:58:03 +02:00
var now = new Date ( ) ;
2015-05-27 21:53:15 +02:00
if ( ! this . lastStanzaDate ) {
this . lastStanzaDate = now ;
2015-05-27 20:58:02 +02:00
}
2015-05-27 21:53:15 +02:00
if ( ( now - this . lastStanzaDate ) / 1000 > this . ping _interval ) {
return this . ping ( ) ;
2015-05-27 18:14:58 +02:00
}
2015-05-28 10:58:25 +02:00
return true ;
2015-10-07 10:07:05 +02:00
} . bind ( converse ) ) ;
2015-05-27 18:14:58 +02:00
}
} ;
2014-02-11 12:44:27 +01:00
this . onReconnected = function ( ) {
// We need to re-register all the event handlers on the newly
// created connection.
2015-06-27 06:36:25 +02:00
this . initStatus ( function ( ) {
2015-05-27 18:14:58 +02:00
this . registerPingHandler ( ) ;
2015-05-21 17:04:56 +02:00
this . rosterview . registerRosterXHandler ( ) ;
this . rosterview . registerPresenceHandler ( ) ;
2014-02-11 12:44:27 +01:00
this . chatboxes . registerMessageHandler ( ) ;
2015-05-21 17:04:56 +02:00
this . xmppstatus . sendPresence ( ) ;
2014-12-31 16:58:15 +01:00
this . giveFeedback ( _ _ ( 'Contacts' ) ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-02-11 12:44:27 +01:00
} ;
2014-04-19 06:58:26 +02:00
this . enableCarbons = function ( ) {
/ * A s k t h e X M P P s e r v e r t o e n a b l e M e s s a g e C a r b o n s
* See XEP - 0280 https : //xmpp.org/extensions/xep-0280.html#enabling
* /
2015-01-17 13:24:05 +01:00
if ( ! this . message _carbons || this . session . get ( 'carbons_enabled' ) ) {
2014-04-19 06:58:26 +02:00
return ;
}
var carbons _iq = new Strophe . Builder ( 'iq' , {
from : this . connection . jid ,
id : 'enablecarbons' ,
type : 'set'
} )
2015-07-10 15:07:32 +02:00
. c ( 'enable' , { xmlns : Strophe . NS . CARBONS } ) ;
2015-06-27 06:36:25 +02:00
this . connection . addHandler ( function ( iq ) {
2014-11-15 15:38:15 +01:00
if ( $ ( iq ) . find ( 'error' ) . length > 0 ) {
converse . log ( 'ERROR: An error occured while trying to enable message carbons.' ) ;
} else {
2015-01-17 13:24:05 +01:00
this . session . save ( { carbons _enabled : true } ) ;
converse . log ( 'Message carbons have been enabled.' ) ;
2014-11-15 15:38:15 +01:00
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) , null , "iq" , null , "enablecarbons" ) ;
2015-01-17 13:24:05 +01:00
this . connection . send ( carbons _iq ) ;
2014-04-19 06:58:26 +02:00
} ;
2013-10-05 22:34:47 +02:00
this . onConnected = function ( ) {
2014-09-07 13:18:36 +02:00
// When reconnecting, there might be some open chat boxes. We don't
// know whether these boxes are of the same account or not, so we
// close them now.
this . chatboxviews . closeAllChatBoxes ( ) ;
2014-09-20 15:07:55 +02:00
this . jid = this . connection . jid ;
2013-10-05 22:34:47 +02:00
this . bare _jid = Strophe . getBareJidFromJid ( this . connection . jid ) ;
2015-07-01 19:43:58 +02:00
this . resource = Strophe . getResourceFromJid ( this . connection . jid ) ;
2013-10-05 22:34:47 +02:00
this . domain = Strophe . getDomainFromJid ( this . connection . jid ) ;
2014-06-08 21:43:00 +02:00
this . minimized _chats = new converse . MinimizedChats ( { model : this . chatboxes } ) ;
2013-10-05 22:34:47 +02:00
this . features = new this . Features ( ) ;
2014-04-19 06:58:26 +02:00
this . enableCarbons ( ) ;
2015-06-27 06:36:25 +02:00
this . initStatus ( function ( ) {
2015-05-27 18:14:58 +02:00
this . registerPingHandler ( ) ;
2015-06-22 00:01:31 +02:00
this . registerIntervalHandler ( ) ;
2013-10-05 22:34:47 +02:00
this . chatboxes . onConnected ( ) ;
2014-12-31 16:58:15 +01:00
this . giveFeedback ( _ _ ( 'Contacts' ) ) ;
2013-11-03 10:36:31 +01:00
if ( this . callback ) {
if ( this . connection . service === 'jasmine tests' ) {
// XXX: Call back with the internal converse object. This
// object should never be exposed to production systems.
// 'jasmine tests' is an invalid http bind service value,
// so we're sure that this is just for tests.
this . callback ( this ) ;
} else {
this . callback ( ) ;
}
2013-10-05 22:34:47 +02:00
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'ready' ) ;
2013-10-05 22:34:47 +02:00
} ;
// Backbone Models and Views
// -------------------------
2014-02-12 06:12:00 +01:00
this . OTR = Backbone . Model . extend ( {
// A model for managing OTR settings.
getSessionPassphrase : function ( ) {
2015-03-22 12:14:45 +01:00
if ( converse . authentication === 'prebind' ) {
2014-04-19 05:12:24 +02:00
var key = b64 _sha1 ( converse . connection . jid ) ,
2014-02-12 06:12:00 +01:00
pass = window . sessionStorage [ key ] ;
if ( typeof pass === 'undefined' ) {
pass = Math . floor ( Math . random ( ) * 4294967295 ) . toString ( ) ;
window . sessionStorage [ key ] = pass ;
}
return pass ;
} else {
return converse . connection . pass ;
}
} ,
2015-10-25 18:49:35 +01:00
generatePrivateKey : function ( instance _tag ) {
2014-02-12 06:12:00 +01:00
var key = new DSA ( ) ;
var jid = converse . connection . jid ;
if ( converse . cache _otr _key ) {
var cipher = CryptoJS . lib . PasswordBasedCipher ;
var pass = this . getSessionPassphrase ( ) ;
if ( typeof pass !== "undefined" ) {
// Encrypt the key and set in sessionStorage. Also store instance tag.
2014-04-19 05:12:24 +02:00
window . sessionStorage [ b64 _sha1 ( jid + 'priv_key' ) ] =
2014-02-12 06:12:00 +01:00
cipher . encrypt ( CryptoJS . algo . AES , key . packPrivate ( ) , pass ) . toString ( ) ;
2014-04-19 05:12:24 +02:00
window . sessionStorage [ b64 _sha1 ( jid + 'instance_tag' ) ] = instance _tag ;
window . sessionStorage [ b64 _sha1 ( jid + 'pass_check' ) ] =
2014-02-12 06:12:00 +01:00
cipher . encrypt ( CryptoJS . algo . AES , 'match' , pass ) . toString ( ) ;
}
}
2014-02-12 10:30:05 +01:00
return key ;
2014-02-12 06:12:00 +01:00
}
} ) ;
2015-09-30 14:18:56 +02:00
this . Message = Backbone . Model . extend ( {
idAttribute : 'msgid' ,
defaults : function ( ) {
return {
msgid : converse . connection . getUniqueId ( )
} ;
}
} ) ;
2013-06-02 00:21:06 +02:00
this . Messages = Backbone . Collection . extend ( {
2015-07-19 23:23:02 +02:00
model : converse . Message ,
comparator : 'time'
2013-06-02 00:21:06 +02:00
} ) ;
this . ChatBox = Backbone . Model . extend ( {
2015-09-24 15:52:35 +02:00
2013-06-02 00:21:06 +02:00
initialize : function ( ) {
2015-09-24 19:41:35 +02:00
var height = this . get ( 'height' ) ,
width = this . get ( 'width' ) ,
settings = {
'height' : converse . applyDragResistance ( height , this . get ( 'default_height' ) ) ,
'width' : converse . applyDragResistance ( width , this . get ( 'default_width' ) ) ,
'num_unread' : this . get ( 'num_unread' ) || 0
} ;
2013-06-02 00:21:06 +02:00
if ( this . get ( 'box_id' ) !== 'controlbox' ) {
this . messages = new converse . Messages ( ) ;
2014-06-30 18:53:58 +02:00
this . messages . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
2014-04-19 05:12:24 +02:00
b64 _sha1 ( 'converse.messages' + this . get ( 'jid' ) + converse . bare _jid ) ) ;
2015-09-24 19:41:35 +02:00
this . save ( _ . extend ( settings , {
2015-01-09 09:02:35 +01:00
// The chat_state will be set to ACTIVE once the chat box is opened
// and we listen for change:chat_state, so shouldn't set it to ACTIVE here.
'chat_state' : undefined ,
2014-04-19 05:12:24 +02:00
'box_id' : b64 _sha1 ( this . get ( 'jid' ) ) ,
2014-03-01 00:51:07 +01:00
'minimized' : this . get ( 'minimized' ) || false ,
2014-06-29 15:59:39 +02:00
'otr_status' : this . get ( 'otr_status' ) || UNENCRYPTED ,
2014-04-25 22:56:59 +02:00
'time_minimized' : this . get ( 'time_minimized' ) || moment ( ) ,
2014-06-08 21:43:00 +02:00
'time_opened' : this . get ( 'time_opened' ) || moment ( ) . valueOf ( ) ,
2015-01-01 21:25:12 +01:00
'url' : '' ,
'user_id' : Strophe . getNodeFromJid ( this . get ( 'jid' ) )
2015-09-24 19:41:35 +02:00
} ) ) ;
2014-03-01 07:21:36 +01:00
} else {
2015-09-24 19:41:35 +02:00
this . set ( _ . extend ( settings , { 'time_opened' : moment ( 0 ) . valueOf ( ) } ) ) ;
2013-03-21 13:11:45 +01:00
}
2013-06-02 00:21:06 +02:00
} ,
2014-06-08 21:43:00 +02:00
maximize : function ( ) {
this . save ( {
'minimized' : false ,
'time_opened' : moment ( ) . valueOf ( )
} ) ;
} ,
minimize : function ( ) {
this . save ( {
'minimized' : true ,
'time_minimized' : moment ( ) . format ( )
} ) ;
} ,
2014-01-31 04:50:38 +01:00
getSession : function ( callback ) {
2013-11-13 07:16:53 +01:00
var cipher = CryptoJS . lib . PasswordBasedCipher ;
2015-10-25 18:49:35 +01:00
var pass , instance _tag , saved _key , pass _check ;
2014-01-31 17:07:44 +01:00
if ( converse . cache _otr _key ) {
2014-02-12 06:12:00 +01:00
pass = converse . otr . getSessionPassphrase ( ) ;
2014-01-31 17:07:44 +01:00
if ( typeof pass !== "undefined" ) {
2014-04-19 05:12:24 +02:00
instance _tag = window . sessionStorage [ b64 _sha1 ( this . id + 'instance_tag' ) ] ;
saved _key = window . sessionStorage [ b64 _sha1 ( this . id + 'priv_key' ) ] ;
pass _check = window . sessionStorage [ b64 _sha1 ( this . connection . jid + 'pass_check' ) ] ;
2014-01-31 17:07:44 +01:00
if ( saved _key && instance _tag && typeof pass _check !== 'undefined' ) {
var decrypted = cipher . decrypt ( CryptoJS . algo . AES , saved _key , pass ) ;
var key = DSA . parsePrivate ( decrypted . toString ( CryptoJS . enc . Latin1 ) ) ;
if ( cipher . decrypt ( CryptoJS . algo . AES , pass _check , pass ) . toString ( CryptoJS . enc . Latin1 ) === 'match' ) {
// Verified that the passphrase is still the same
this . trigger ( 'showHelpMessages' , [ _ _ ( 'Re-establishing encrypted session' ) ] ) ;
callback ( {
'key' : key ,
'instance_tag' : instance _tag
} ) ;
return ; // Our work is done here
}
2014-01-31 04:50:38 +01:00
}
2013-08-31 20:44:43 +02:00
}
2013-10-20 22:20:45 +02:00
}
2013-09-10 22:37:12 +02:00
// We need to generate a new key and instance tag
2014-01-31 12:40:33 +01:00
this . trigger ( 'showHelpMessages' , [
_ _ ( 'Generating private key.' ) ,
_ _ ( 'Your browser might become unresponsive.' ) ] ,
null ,
true // show spinner
) ;
2014-02-12 10:30:05 +01:00
setTimeout ( function ( ) {
2015-10-25 18:49:35 +01:00
var instance _tag = OTR . makeInstanceTag ( ) ;
2014-02-12 10:30:05 +01:00
callback ( {
2015-10-25 18:49:35 +01:00
'key' : converse . otr . generatePrivateKey . call ( this , instance _tag ) ,
'instance_tag' : instance _tag
2014-02-12 10:30:05 +01:00
} ) ;
} , 500 ) ;
2013-08-16 15:48:23 +02:00
} ,
2013-08-31 22:28:33 +02:00
updateOTRStatus : function ( state ) {
switch ( state ) {
2013-11-13 07:16:53 +01:00
case OTR . CONST . STATUS _AKE _SUCCESS :
if ( this . otr . msgstate === OTR . CONST . MSGSTATE _ENCRYPTED ) {
2013-08-31 22:28:33 +02:00
this . save ( { 'otr_status' : UNVERIFIED } ) ;
}
break ;
2013-11-13 07:16:53 +01:00
case OTR . CONST . STATUS _END _OTR :
if ( this . otr . msgstate === OTR . CONST . MSGSTATE _FINISHED ) {
2013-08-31 22:28:33 +02:00
this . save ( { 'otr_status' : FINISHED } ) ;
2013-11-13 07:16:53 +01:00
} else if ( this . otr . msgstate === OTR . CONST . MSGSTATE _PLAINTEXT ) {
2013-08-31 22:28:33 +02:00
this . save ( { 'otr_status' : UNENCRYPTED } ) ;
}
break ;
}
} ,
onSMP : function ( type , data ) {
// Event handler for SMP (Socialist's Millionaire Protocol)
// used by OTR (off-the-record).
switch ( type ) {
case 'question' :
2013-08-31 23:20:07 +02:00
this . otr . smpSecret ( prompt ( _ _ (
2014-11-09 18:07:06 +01:00
'Authentication request from %1$s\n\nYour chat contact is attempting to verify your identity, by asking you the question below.\n\n%2$s' ,
2013-09-10 22:52:15 +02:00
[ this . get ( 'fullname' ) , data ] ) ) ) ;
2013-08-31 22:28:33 +02:00
break ;
case 'trust' :
2013-11-11 20:45:18 +01:00
if ( data === true ) {
2013-08-31 22:28:33 +02:00
this . save ( { 'otr_status' : VERIFIED } ) ;
} else {
2013-09-01 21:37:22 +02:00
this . trigger (
'showHelpMessages' ,
2013-09-10 22:52:15 +02:00
[ _ _ ( "Could not verify this user's identify." ) ] ,
2013-09-01 21:37:22 +02:00
'error' ) ;
2013-08-31 22:28:33 +02:00
this . save ( { 'otr_status' : UNVERIFIED } ) ;
}
break ;
default :
2015-03-31 15:26:57 +02:00
throw new TypeError ( 'ChatBox.onSMP: Unknown type for SMP' ) ;
2013-08-31 22:28:33 +02:00
}
} ,
2013-09-01 21:37:22 +02:00
initiateOTR : function ( query _msg ) {
// Sets up an OTR object through which we can send and receive
// encrypted messages.
//
// If 'query_msg' is passed in, it means there is an alread incoming
2014-11-09 18:07:06 +01:00
// query message from our contact. Otherwise, it is us who will
2013-09-01 21:37:22 +02:00
// send the query message to them.
2013-09-10 22:37:12 +02:00
this . save ( { 'otr_status' : UNENCRYPTED } ) ;
2015-10-25 18:49:35 +01:00
this . getSession ( function ( session ) {
2014-01-31 04:50:38 +01:00
this . otr = new OTR ( {
fragment _size : 140 ,
send _interval : 200 ,
priv : session . key ,
instance _tag : session . instance _tag ,
debug : this . debug
} ) ;
2015-06-27 06:36:25 +02:00
this . otr . on ( 'status' , this . updateOTRStatus . bind ( this ) ) ;
this . otr . on ( 'smp' , this . onSMP . bind ( this ) ) ;
2013-09-01 21:37:22 +02:00
2015-06-27 06:36:25 +02:00
this . otr . on ( 'ui' , function ( msg ) {
2014-01-31 04:50:38 +01:00
this . trigger ( 'showReceivedOTRMessage' , msg ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
this . otr . on ( 'io' , function ( msg ) {
2015-09-30 14:18:56 +02:00
this . trigger ( 'sendMessage' , new converse . Message ( { message : msg } ) ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
this . otr . on ( 'error' , function ( msg ) {
2014-01-31 04:50:38 +01:00
this . trigger ( 'showOTRError' , msg ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2013-09-08 18:13:49 +02:00
2014-11-09 18:07:06 +01:00
this . trigger ( 'showHelpMessages' , [ _ _ ( 'Exchanging private key with contact.' ) ] ) ;
2014-01-31 04:50:38 +01:00
if ( query _msg ) {
this . otr . receiveMsg ( query _msg ) ;
} else {
this . otr . sendQueryMsg ( ) ;
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2013-08-16 15:48:23 +02:00
} ,
2013-09-08 15:54:04 +02:00
endOTR : function ( ) {
if ( this . otr ) {
this . otr . endOtr ( ) ;
}
this . save ( { 'otr_status' : UNENCRYPTED } ) ;
} ,
2015-07-17 23:13:43 +02:00
createMessage : function ( $message , $delay , archive _id ) {
2015-07-17 16:49:58 +02:00
$delay = $delay || $message . find ( 'delay' ) ;
2014-04-19 06:58:26 +02:00
var body = $message . children ( 'body' ) . text ( ) ,
2015-07-17 16:49:58 +02:00
delayed = $delay . length > 0 ,
2013-11-04 14:57:22 +01:00
fullname = this . get ( 'fullname' ) ,
2014-09-12 22:23:42 +02:00
is _groupchat = $message . attr ( 'type' ) === 'groupchat' ,
2014-10-15 19:49:16 +02:00
msgid = $message . attr ( 'id' ) ,
2015-01-09 10:48:36 +01:00
chat _state = $message . find ( COMPOSING ) . length && COMPOSING ||
$message . find ( PAUSED ) . length && PAUSED ||
$message . find ( INACTIVE ) . length && INACTIVE ||
$message . find ( ACTIVE ) . length && ACTIVE ||
$message . find ( GONE ) . length && GONE ,
2015-05-15 00:18:10 +02:00
stamp , time , sender , from ;
2014-09-12 22:23:42 +02:00
if ( is _groupchat ) {
from = Strophe . unescapeNode ( Strophe . getResourceFromJid ( $message . attr ( 'from' ) ) ) ;
} else {
from = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ;
}
2015-01-09 10:48:36 +01:00
fullname = ( _ . isEmpty ( fullname ) ? from : fullname ) . split ( ' ' ) [ 0 ] ;
if ( delayed ) {
2015-07-17 16:49:58 +02:00
stamp = $delay . attr ( 'stamp' ) ;
2015-01-09 10:48:36 +01:00
time = stamp ;
2013-03-21 13:11:45 +01:00
} else {
2015-01-09 10:48:36 +01:00
time = moment ( ) . format ( ) ;
}
2015-10-25 18:49:35 +01:00
if ( ( is _groupchat && from === this . get ( 'nick' ) ) || ( ! is _groupchat && from === converse . bare _jid ) ) {
2015-01-09 10:48:36 +01:00
sender = 'me' ;
} else {
sender = 'them' ;
2013-03-21 13:11:45 +01:00
}
2015-01-09 10:48:36 +01:00
this . messages . create ( {
chat _state : chat _state ,
delayed : delayed ,
fullname : fullname ,
message : body || undefined ,
msgid : msgid ,
sender : sender ,
2015-07-17 23:13:43 +02:00
time : time ,
archive _id : archive _id
2015-01-09 10:48:36 +01:00
} ) ;
2013-08-24 23:09:54 +02:00
} ,
2015-07-17 23:13:43 +02:00
receiveMessage : function ( $message , $delay , archive _id ) {
2014-04-19 06:58:26 +02:00
var $body = $message . children ( 'body' ) ;
2013-12-18 03:15:27 +01:00
var text = ( $body . length > 0 ? $body . text ( ) : undefined ) ;
2013-09-08 15:54:04 +02:00
if ( ( ! text ) || ( ! converse . allow _otr ) ) {
2015-07-17 23:13:43 +02:00
return this . createMessage ( $message , $delay , archive _id ) ;
2013-09-08 15:54:04 +02:00
}
2014-01-31 12:40:33 +01:00
if ( text . match ( /^\?OTRv23?/ ) ) {
this . initiateOTR ( text ) ;
2013-09-08 15:54:04 +02:00
} else {
2014-01-31 12:40:33 +01:00
if ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . get ( 'otr_status' ) ) ) {
this . otr . receiveMsg ( text ) ;
} else {
if ( text . match ( /^\?OTR/ ) ) {
if ( ! this . otr ) {
this . initiateOTR ( text ) ;
} else {
this . otr . receiveMsg ( text ) ;
}
2013-08-24 23:09:54 +02:00
} else {
2014-01-31 12:40:33 +01:00
// Normal unencrypted message.
2015-07-17 23:13:43 +02:00
this . createMessage ( $message , $delay , archive _id ) ;
2013-08-24 23:09:54 +02:00
}
}
}
2013-05-30 18:06:40 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
this . ChatBoxView = Backbone . View . extend ( {
length : 200 ,
tagName : 'div' ,
className : 'chatbox' ,
2013-10-20 18:13:34 +02:00
is _chatroom : false , // This is not a multi-user chatroom
2013-06-02 00:21:06 +02:00
events : {
2014-05-27 18:34:22 +02:00
'click .close-chatbox-button' : 'close' ,
2014-06-02 04:47:23 +02:00
'click .toggle-chatbox-button' : 'minimize' ,
2013-08-25 22:10:32 +02:00
'keypress textarea.chat-textarea' : 'keyPressed' ,
2013-10-20 18:13:34 +02:00
'click .toggle-smiley' : 'toggleEmoticonMenu' ,
2013-11-03 21:28:44 +01:00
'click .toggle-smiley ul li' : 'insertEmoticon' ,
2014-04-24 18:03:30 +02:00
'click .toggle-clear' : 'clearMessages' ,
2013-08-25 22:10:32 +02:00
'click .toggle-otr' : 'toggleOTRMenu' ,
2013-08-30 21:18:46 +02:00
'click .start-otr' : 'startOTRFromToolbar' ,
2013-08-25 22:10:32 +02:00
'click .end-otr' : 'endOTR' ,
2013-12-18 03:52:16 +01:00
'click .auth-otr' : 'authOTR' ,
2014-02-22 22:20:36 +01:00
'click .toggle-call' : 'toggleCall' ,
2015-09-24 19:41:35 +02:00
'mousedown .dragresize-top' : 'onStartVerticalResize' ,
'mousedown .dragresize-left' : 'onStartHorizontalResize' ,
'mousedown .dragresize-topleft' : 'onStartDiagonalResize'
2013-06-02 00:21:06 +02:00
} ,
2015-05-27 20:58:02 +02:00
initialize : function ( ) {
2015-11-05 11:02:23 +01:00
$ ( window ) . on ( 'resize' , _ . debounce ( this . setDimensions . bind ( this ) , 100 ) ) ;
2015-07-19 23:23:02 +02:00
this . model . messages . on ( 'add' , this . onMessageAdded , this ) ;
2013-08-16 16:05:24 +02:00
this . model . on ( 'show' , this . show , this ) ;
this . model . on ( 'destroy' , this . hide , this ) ;
2015-01-01 21:25:12 +01:00
// TODO check for changed fullname as well
this . model . on ( 'change:chat_state' , this . sendChatState , this ) ;
this . model . on ( 'change:chat_status' , this . onChatStatusChanged , this ) ;
this . model . on ( 'change:image' , this . renderAvatar , this ) ;
this . model . on ( 'change:otr_status' , this . onOTRStatusChanged , this ) ;
this . model . on ( 'change:minimized' , this . onMinimizedChanged , this ) ;
this . model . on ( 'change:status' , this . onStatusChanged , this ) ;
2013-08-31 15:45:23 +02:00
this . model . on ( 'showOTRError' , this . showOTRError , this ) ;
2013-08-31 15:00:04 +02:00
this . model . on ( 'showHelpMessages' , this . showHelpMessages , this ) ;
2015-07-04 10:10:21 +02:00
this . model . on ( 'sendMessage' , this . sendMessage , this ) ;
2013-08-25 12:17:46 +02:00
this . model . on ( 'showSentOTRMessage' , function ( text ) {
2014-03-05 00:23:45 +01:00
this . showMessage ( { 'message' : text , 'sender' : 'me' } ) ;
2013-08-25 12:06:53 +02:00
} , this ) ;
2013-08-25 12:17:46 +02:00
this . model . on ( 'showReceivedOTRMessage' , function ( text ) {
2014-03-05 00:23:45 +01:00
this . showMessage ( { 'message' : text , 'sender' : 'them' } ) ;
2013-08-25 12:06:53 +02:00
} , this ) ;
2015-07-17 20:11:13 +02:00
this . updateVCard ( ) . render ( ) . fetchMessages ( ) . insertIntoPage ( ) . hide ( ) ;
2015-07-17 16:49:58 +02:00
2014-02-28 13:22:15 +01:00
if ( ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . model . get ( 'otr_status' ) ) ) || converse . use _otr _by _default ) {
this . model . initiateOTR ( ) ;
}
} ,
2014-02-23 01:54:35 +01:00
2013-08-30 22:49:33 +02:00
render : function ( ) {
this . $el . attr ( 'id' , this . model . get ( 'box_id' ) )
2014-04-24 18:03:30 +02:00
. html ( converse . templates . chatbox (
2014-04-24 07:58:35 +02:00
_ . extend ( this . model . toJSON ( ) , {
2013-12-30 20:27:57 +01:00
show _toolbar : converse . show _toolbar ,
2015-10-31 19:29:43 +01:00
info _close : _ _ ( 'Close this chat box' ) ,
info _minimize : _ _ ( 'Minimize this chat box' ) ,
info _view : _ _ ( 'View more information on this person' ) ,
2013-12-30 20:27:57 +01:00
label _personal _message : _ _ ( 'Personal message' )
}
)
)
) ;
2015-09-26 00:11:21 +02:00
this . setWidth ( ) ;
2015-07-19 12:08:43 +02:00
this . $content = this . $el . find ( '.chat-content' ) ;
2013-08-30 22:49:33 +02:00
this . renderToolbar ( ) . renderAvatar ( ) ;
2015-07-19 12:08:43 +02:00
this . $content . on ( 'scroll' , _ . debounce ( this . onScroll . bind ( this ) , 100 ) ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxOpened' , this ) ;
2015-06-22 00:01:31 +02:00
setTimeout ( converse . refreshWebkit , 50 ) ;
2014-06-02 04:47:23 +02:00
return this . showStatusMessage ( ) ;
2013-08-30 22:49:33 +02:00
} ,
2015-09-26 00:11:21 +02:00
setWidth : function ( ) {
// If a custom width is applied (due to drag-resizing),
// then we need to set the width of the .chatbox element as well.
if ( this . model . get ( 'width' ) ) {
this . $el . css ( 'width' , this . model . get ( 'width' ) ) ;
}
} ,
2015-07-17 20:11:13 +02:00
onScroll : function ( ev ) {
2015-07-19 23:23:02 +02:00
if ( $ ( ev . target ) . scrollTop ( ) === 0 && this . model . messages . length ) {
this . fetchArchivedMessages ( {
'before' : this . model . messages . at ( 0 ) . get ( 'archive_id' ) ,
'with' : this . model . get ( 'jid' ) ,
2015-07-22 00:18:30 +02:00
'max' : converse . archived _messages _page _size
2015-07-19 23:23:02 +02:00
} ) ;
2015-07-17 20:11:13 +02:00
}
} ,
2015-07-17 16:49:58 +02:00
fetchMessages : function ( ) {
/ * R e s p o n s i b l e f o r f e t c h i n g p r e v i o u s l y s e n t m e s s a g e s , f i r s t
* from session storage , and then once that ' s done by calling
* fetchArchivedMessages , which fetches from the XMPP server if
* applicable .
* /
2015-07-17 20:11:13 +02:00
this . model . messages . fetch ( {
2015-07-17 16:49:58 +02:00
'add' : true ,
2015-07-20 09:57:28 +02:00
'success' : function ( ) {
if ( ! converse . features . findWhere ( { 'var' : Strophe . NS . MAM } ) ) {
return ;
}
2015-07-22 00:18:30 +02:00
if ( this . model . messages . length < converse . archived _messages _page _size ) {
2015-07-18 00:08:21 +02:00
this . fetchArchivedMessages ( {
'before' : '' , // Page backwards from the most recent message
'with' : this . model . get ( 'jid' ) ,
2015-07-22 00:18:30 +02:00
'max' : converse . archived _messages _page _size
2015-07-18 00:08:21 +02:00
} ) ;
}
2015-07-17 16:49:58 +02:00
} . bind ( this )
2015-07-20 09:57:28 +02:00
} ) ;
return this ;
2015-07-17 16:49:58 +02:00
} ,
2015-07-17 23:13:43 +02:00
fetchArchivedMessages : function ( options ) {
2015-07-17 20:11:13 +02:00
/ * F e t c h a r c h i v e d c h a t m e s s a g e s f r o m t h e X M P P s e r v e r .
2015-07-17 16:49:58 +02:00
*
* Then , upon receiving them , call onMessage on the chat box ,
* so that they are displayed inside it .
* /
2015-10-12 15:19:46 +02:00
if ( ! converse . features . findWhere ( { 'var' : Strophe . NS . MAM } ) ) {
converse . log ( "Attempted to fetch archived messages but this user's server doesn't support XEP-0313" ) ;
return ;
}
this . addSpinner ( ) ;
2015-07-21 11:38:44 +02:00
API . archive . query ( _ . extend ( options , { 'groupchat' : this . is _chatroom } ) ,
2015-07-18 21:39:49 +02:00
function ( messages ) {
this . clearSpinner ( ) ;
if ( messages . length ) {
2015-07-21 20:10:16 +02:00
if ( this . is _chatroom ) {
_ . map ( messages , this . onChatRoomMessage . bind ( this ) ) ;
} else {
_ . map ( messages , converse . chatboxes . onMessage . bind ( converse . chatboxes ) ) ;
}
2015-07-18 21:39:49 +02:00
}
} . bind ( this ) ,
2015-10-25 18:49:35 +01:00
function ( ) {
2015-07-21 21:21:33 +02:00
this . clearSpinner ( ) ;
converse . log ( "Error while trying to fetch archived messages" , "error" ) ;
} . bind ( this )
2015-07-17 20:11:13 +02:00
) ;
2015-07-17 16:49:58 +02:00
} ,
2015-07-17 16:42:00 +02:00
insertIntoPage : function ( ) {
this . $el . insertAfter ( converse . chatboxviews . get ( "controlbox" ) . $el ) ;
return this ;
} ,
2015-11-05 11:02:23 +01:00
adjustToViewport : function ( ) {
/ * E v e n t h a n d l e r c a l l e d w h e n v i e w p o r t g e t s r e s i z e d . W e r e m o v e
* custom width / height from chat boxes .
* /
var viewport _width = Math . max ( document . documentElement . clientWidth , window . innerWidth || 0 ) ;
var viewport _height = Math . max ( document . documentElement . clientHeight , window . innerHeight || 0 ) ;
if ( viewport _width <= 480 ) {
this . model . set ( 'height' , undefined ) ;
this . model . set ( 'width' , undefined ) ;
} else if ( viewport _width <= this . model . get ( 'width' ) ) {
this . model . set ( 'width' , undefined ) ;
} else if ( viewport _height <= this . model . get ( 'height' ) ) {
this . model . set ( 'height' , undefined ) ;
}
} ,
2014-02-28 19:55:46 +01:00
initDragResize : function ( ) {
2015-09-24 19:41:35 +02:00
/ * D e t e r m i n e a n d s t o r e t h e d e f a u l t b o x s i z e .
* We need this information for the drag - resizing feature .
* /
2015-09-25 23:00:38 +02:00
var $flyout = this . $el . find ( '.box-flyout' ) ;
2015-10-25 18:49:35 +01:00
if ( typeof this . model . get ( 'height' ) === 'undefined' ) {
2015-09-24 19:41:35 +02:00
var height = $flyout . height ( ) ;
var width = $flyout . width ( ) ;
this . model . set ( 'height' , height ) ;
this . model . set ( 'default_height' , height ) ;
this . model . set ( 'width' , width ) ;
this . model . set ( 'default_width' , width ) ;
}
2015-09-25 23:00:38 +02:00
var min _width = $flyout . css ( 'min-width' ) ;
var min _height = $flyout . css ( 'min-height' ) ;
this . model . set ( 'min_width' , min _width . endsWith ( 'px' ) ? Number ( min _width . replace ( /px$/ , '' ) ) : 0 ) ;
this . model . set ( 'min_height' , min _height . endsWith ( 'px' ) ? Number ( min _height . replace ( /px$/ , '' ) ) : 0 ) ;
2015-09-24 19:41:35 +02:00
// Initialize last known mouse position
this . prev _pageY = 0 ;
this . prev _pageX = 0 ;
2014-09-06 23:34:39 +02:00
if ( converse . connection . connected ) {
2014-02-28 19:55:46 +01:00
this . height = this . model . get ( 'height' ) ;
2015-09-24 19:41:35 +02:00
this . width = this . model . get ( 'width' ) ;
2014-02-28 19:55:46 +01:00
}
2014-03-01 00:51:07 +01:00
return this ;
2014-02-28 19:55:46 +01:00
} ,
2015-11-05 11:02:23 +01:00
setDimensions : function ( ) {
// Make sure the chat box has the right height and width.
this . adjustToViewport ( ) ;
this . setChatBoxHeight ( this . model . get ( 'height' ) ) ;
this . setChatBoxWidth ( this . model . get ( 'width' ) ) ;
} ,
2015-10-31 18:38:51 +01:00
clearStatusNotification : function ( ) {
this . $content . find ( 'div.chat-event' ) . remove ( ) ;
} ,
2014-09-05 19:36:31 +02:00
showStatusNotification : function ( message , keep _old ) {
if ( ! keep _old ) {
2015-10-31 18:38:51 +01:00
this . clearStatusNotification ( ) ;
2014-09-05 19:36:31 +02:00
}
2015-10-28 09:49:28 +01:00
this . $content . append ( $ ( '<div class="chat-info chat-event"></div>' ) . text ( message ) ) ;
2013-08-25 12:06:53 +02:00
this . scrollDown ( ) ;
} ,
2014-04-24 18:03:30 +02:00
clearChatRoomMessages : function ( ev ) {
2014-10-15 22:56:45 +02:00
if ( typeof ev !== "undefined" ) { ev . stopPropagation ( ) ; }
2014-04-24 18:03:30 +02:00
var result = confirm ( _ _ ( "Are you sure you want to clear the messages from this room?" ) ) ;
if ( result === true ) {
2015-07-19 12:08:43 +02:00
this . $content . empty ( ) ;
2014-04-24 18:03:30 +02:00
}
return this ;
} ,
2015-10-12 15:19:46 +02:00
addSpinner : function ( ) {
if ( ! this . $content . first ( ) . hasClass ( 'spinner' ) ) {
this . $content . prepend ( '<span class="spinner"/>' ) ;
}
} ,
2015-07-18 21:39:49 +02:00
clearSpinner : function ( ) {
2015-07-19 12:08:43 +02:00
if ( this . $content . children ( ':first' ) . is ( 'span.spinner' ) ) {
2015-07-19 13:43:26 +02:00
this . $content . children ( ':first' ) . remove ( ) ;
2015-07-18 21:39:49 +02:00
}
} ,
2015-07-19 23:23:02 +02:00
prependDayIndicator : function ( date ) {
/ * P r e p e n d s a n i n d i c a t o r i n t o t h e c h a t a r e a , s h o w i n g t h e d a y a s
* given by the passed in date .
*
* Parameters :
* ( String ) date - An ISO8601 date string .
* /
var day _date = moment ( date ) . startOf ( 'day' ) ;
this . $content . prepend ( converse . templates . new _day ( {
isodate : day _date . format ( ) ,
datestring : day _date . format ( "dddd MMM Do YYYY" )
} ) ) ;
} ,
2015-07-20 20:07:29 +02:00
appendMessage : function ( attrs ) {
/ * H e l p e r m e t h o d w h i c h a p p e n d s a m e s s a g e t o t h e e n d o f t h e c h a t
* box ' s content area .
*
* Parameters :
* ( Object ) attrs : An object containing the message attributes .
* /
_ . compose (
_ . debounce ( this . scrollDown . bind ( this ) , 50 ) ,
this . $content . append . bind ( this . $content )
) ( this . renderMessage ( attrs ) ) ;
} ,
2015-07-19 23:23:02 +02:00
showMessage : function ( attrs ) {
/ * I n s e r t s a c h a t m e s s a g e i n t o t h e c o n t e n t a r e a o f t h e c h a t b o x .
* Will also insert a new day indicator if the message is on a
* different day .
*
* The message to show may either be newer than the newest
* message , or older than the oldest message .
*
* Parameters :
* ( Object ) attrs : An object containing the message attributes .
* /
2015-07-20 20:07:29 +02:00
var $first _msg = this . $content . children ( '.chat-message:first' ) ,
2015-07-19 23:23:02 +02:00
first _msg _date = $first _msg . data ( 'isodate' ) ,
2015-07-20 20:07:29 +02:00
last _msg _date , current _msg _date , day _date , $msgs , msg _dates , idx ;
2015-09-01 13:30:51 +02:00
if ( ! first _msg _date ) {
2015-07-20 20:07:29 +02:00
this . appendMessage ( attrs ) ;
return ;
}
current _msg _date = moment ( attrs . time ) || moment ;
2015-07-21 21:21:33 +02:00
last _msg _date = this . $content . children ( '.chat-message:last' ) . data ( 'isodate' ) ;
2015-07-20 20:07:29 +02:00
if ( typeof last _msg _date !== "undefined" && ( current _msg _date . isAfter ( last _msg _date ) || current _msg _date . isSame ( last _msg _date ) ) ) {
// The new message is after the last message
if ( current _msg _date . isAfter ( last _msg _date , 'day' ) ) {
// Append a new day indicator
day _date = moment ( current _msg _date ) . startOf ( 'day' ) ;
this . $content . append ( converse . templates . new _day ( {
isodate : current _msg _date . format ( ) ,
datestring : current _msg _date . format ( "dddd MMM Do YYYY" )
} ) ) ;
}
this . appendMessage ( attrs ) ;
return ;
}
2015-07-19 23:23:02 +02:00
if ( typeof first _msg _date !== "undefined" &&
2015-07-20 20:07:29 +02:00
( current _msg _date . isBefore ( first _msg _date ) ||
2015-07-19 23:23:02 +02:00
( current _msg _date . isSame ( first _msg _date ) && ! current _msg _date . isSame ( last _msg _date ) ) ) ) {
2015-07-20 20:07:29 +02:00
// The new message is before the first message
2015-07-19 23:23:02 +02:00
if ( $first _msg . prev ( ) . length === 0 ) {
// There's no day indicator before the first message, so we prepend one.
this . prependDayIndicator ( first _msg _date ) ;
}
if ( current _msg _date . isBefore ( first _msg _date , 'day' ) ) {
_ . compose (
this . scrollDownMessageHeight . bind ( this ) ,
function ( $el ) {
this . $content . prepend ( $el ) ;
return $el ;
} . bind ( this )
) ( this . renderMessage ( attrs ) ) ;
// This message is on a different day, so we add a day indicator.
this . prependDayIndicator ( current _msg _date ) ;
} else {
// The message is before the first, but on the same day.
// We need to prepend the message immediately before the
// first message (so that it'll still be after the day indicator).
_ . compose (
this . scrollDownMessageHeight . bind ( this ) ,
function ( $el ) {
$el . insertBefore ( $first _msg ) ;
return $el ;
}
) ( this . renderMessage ( attrs ) ) ;
}
2015-07-20 20:07:29 +02:00
} else {
// We need to find the correct place to position the message
current _msg _date = current _msg _date . format ( ) ;
$msgs = this . $content . children ( '.chat-message' ) ;
msg _dates = _ . map ( $msgs , function ( el ) {
return $ ( el ) . data ( 'isodate' ) ;
} ) ;
msg _dates . push ( current _msg _date ) ;
msg _dates . sort ( ) ;
idx = msg _dates . indexOf ( current _msg _date ) - 1 ;
_ . compose (
this . scrollDownMessageHeight . bind ( this ) ,
function ( $el ) {
$el . insertAfter ( this . $content . find ( '.chat-message[data-isodate="' + msg _dates [ idx ] + '"]' ) ) ;
return $el ;
} . bind ( this )
) ( this . renderMessage ( attrs ) ) ;
2015-07-17 20:11:13 +02:00
}
2015-07-19 23:23:02 +02:00
} ,
renderMessage : function ( attrs ) {
/ * R e n d e r s a c h a t m e s s a g e b a s e d o n t h e p a s s e d i n a t t r i b u t e s .
*
* Parameters :
* ( Object ) attrs : An object containing the message attributes .
*
* Returns :
* The DOM element representing the message .
* /
var msg _time = moment ( attrs . time ) || moment ,
text = attrs . message ,
match = text . match ( /^\/(.*?)(?: (.*))?$/ ) ,
fullname = this . model . get ( 'fullname' ) || attrs . fullname ,
extra _classes = attrs . delayed && 'delayed' || '' ,
template , username ;
2013-06-02 00:21:06 +02:00
if ( ( match ) && ( match [ 1 ] === 'me' ) ) {
text = text . replace ( /^\/me/ , '' ) ;
2014-03-09 12:10:57 +01:00
template = converse . templates . action ;
2014-03-05 07:44:01 +01:00
username = fullname ;
2013-06-02 00:21:06 +02:00
} else {
2013-12-30 20:27:57 +01:00
template = converse . templates . message ;
2015-07-19 23:23:02 +02:00
username = attrs . sender === 'me' && _ _ ( 'me' ) || fullname ;
2013-06-02 00:21:06 +02:00
}
2015-07-19 12:08:43 +02:00
this . $content . find ( 'div.chat-event' ) . remove ( ) ;
2014-09-17 21:40:19 +02:00
2015-10-25 18:49:35 +01:00
if ( this . is _chatroom && attrs . sender === 'them' && ( new RegExp ( "\\b" + this . model . get ( 'nick' ) + "\\b" ) ) . test ( text ) ) {
2014-09-17 21:40:19 +02:00
// Add special class to mark groupchat messages in which we
// are mentioned.
extra _classes += ' mentioned' ;
}
2015-07-19 23:23:02 +02:00
return $ ( template ( {
2015-09-30 14:18:56 +02:00
msgid : attrs . msgid ,
2015-07-19 23:23:02 +02:00
'sender' : attrs . sender ,
'time' : msg _time . format ( 'hh:mm' ) ,
'isodate' : msg _time . format ( ) ,
'username' : username ,
'message' : '' ,
'extra_classes' : extra _classes
2015-10-28 09:52:19 +01:00
} ) ) . children ( '.chat-msg-content' ) . first ( ) . text ( text )
2015-07-11 12:18:05 +02:00
. addHyperlinks ( )
2015-07-19 23:23:02 +02:00
. addEmoticons ( converse . visible _toolbar _buttons . emoticons ) . parent ( ) ;
2013-06-02 00:21:06 +02:00
} ,
2014-01-31 12:40:33 +01:00
showHelpMessages : function ( msgs , type , spinner ) {
2015-07-19 12:08:43 +02:00
var i , msgs _length = msgs . length ;
2013-08-25 12:06:53 +02:00
for ( i = 0 ; i < msgs _length ; i ++ ) {
2015-07-19 12:08:43 +02:00
this . $content . append ( $ ( '<div class="chat-' + ( type || 'info' ) + '">' + msgs [ i ] + '</div>' ) ) ;
2013-08-25 12:06:53 +02:00
}
2014-01-31 12:40:33 +01:00
if ( spinner === true ) {
2015-07-19 12:08:43 +02:00
this . $content . append ( '<span class="spinner"/>' ) ;
2014-01-31 12:40:33 +01:00
} else if ( spinner === false ) {
2015-07-19 12:08:43 +02:00
this . $content . find ( 'span.spinner' ) . remove ( ) ;
2014-01-31 12:40:33 +01:00
}
2013-08-31 15:45:23 +02:00
return this . scrollDown ( ) ;
2013-08-25 12:06:53 +02:00
} ,
2015-07-19 23:23:02 +02:00
onMessageAdded : function ( message ) {
/ * H a n d l e r t h a t g e t s c a l l e d w h e n a n e w m e s s a g e o b j e c t i s c r e a t e d .
2015-07-19 13:43:26 +02:00
*
* Parameters :
2015-07-19 23:23:02 +02:00
* ( Object ) message - The message Backbone object that was added .
2015-07-19 12:22:10 +02:00
* /
2015-10-31 18:38:51 +01:00
if ( typeof this . clear _status _timeout !== 'undefined' ) {
clearTimeout ( this . clear _status _timeout ) ;
delete this . clear _status _timeout ;
}
2015-01-09 10:48:36 +01:00
if ( ! message . get ( 'message' ) ) {
if ( message . get ( 'chat_state' ) === COMPOSING ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'is typing' ) ) ;
2015-10-31 18:38:51 +01:00
this . clear _status _timeout = setTimeout ( this . clearStatusNotification . bind ( this ) , 10000 ) ;
2015-01-09 10:48:36 +01:00
return ;
} else if ( message . get ( 'chat_state' ) === PAUSED ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'has stopped typing' ) ) ;
return ;
} else if ( _ . contains ( [ INACTIVE , ACTIVE ] , message . get ( 'chat_state' ) ) ) {
2015-07-19 12:08:43 +02:00
this . $content . find ( 'div.chat-event' ) . remove ( ) ;
2015-01-09 10:48:36 +01:00
return ;
} else if ( message . get ( 'chat_state' ) === GONE ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'has gone away' ) ) ;
return ;
}
2013-06-02 00:21:06 +02:00
} else {
2014-03-05 00:23:45 +01:00
this . showMessage ( _ . clone ( message . attributes ) ) ;
2012-12-06 14:33:43 +01:00
}
2015-10-25 18:49:35 +01:00
if ( ( message . get ( 'sender' ) !== 'me' ) && ( converse . windowState === 'blur' ) ) {
2013-06-02 00:21:06 +02:00
converse . incrementMsgCounter ( ) ;
2012-12-06 14:33:43 +01:00
}
2015-04-02 02:01:53 +02:00
if ( ! this . model . get ( 'minimized' ) && ! this . $el . is ( ':visible' ) ) {
2015-07-20 09:54:56 +02:00
_ . debounce ( this . show . bind ( this ) , 100 ) ( ) ;
2015-04-02 02:01:53 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
2012-09-21 16:04:57 +02:00
2015-09-30 14:18:56 +02:00
sendMessage : function ( message ) {
2015-07-04 10:10:21 +02:00
/ * R e s p o n s i b l e f o r s e n d i n g o f f a t e x t m e s s a g e .
*
* Parameters :
2015-09-30 14:18:56 +02:00
* ( Message ) message - The chat message
2013-08-25 12:17:46 +02:00
* /
2015-07-19 13:43:26 +02:00
// TODO: We might want to send to specfic resources. Especially in the OTR case.
2013-08-25 12:17:46 +02:00
var bare _jid = this . model . get ( 'jid' ) ;
2015-09-30 14:18:56 +02:00
var messageStanza = $msg ( { from : converse . connection . jid , to : bare _jid , type : 'chat' , id : message . get ( 'msgid' ) } )
. c ( 'body' ) . t ( message . get ( 'message' ) ) . up ( )
2015-07-03 11:37:21 +02:00
. c ( ACTIVE , { 'xmlns' : Strophe . NS . CHATSTATES } ) . up ( ) ;
2015-10-25 18:49:35 +01:00
if ( this . model . get ( 'otr_status' ) !== UNENCRYPTED ) {
2015-07-03 11:37:21 +02:00
// OTR messages aren't carbon copied
2015-09-30 14:18:56 +02:00
messageStanza . c ( 'private' , { 'xmlns' : Strophe . NS . CARBONS } ) ;
2015-07-03 11:37:21 +02:00
}
2015-09-30 14:18:56 +02:00
converse . connection . send ( messageStanza ) ;
2014-04-19 06:18:17 +02:00
if ( converse . forward _messages ) {
// Forward the message, so that other connected resources are also aware of it.
2015-10-25 18:49:35 +01:00
converse . connection . send (
$msg ( { to : converse . bare _jid , type : 'chat' , id : message . get ( 'msgid' ) } )
. c ( 'forwarded' , { xmlns : 'urn:xmpp:forward:0' } )
. c ( 'delay' , { xmns : 'urn:xmpp:delay' , stamp : ( new Date ( ) ) . getTime ( ) } ) . up ( )
. cnode ( messageStanza . tree ( ) )
) ;
2014-04-19 06:18:17 +02:00
}
2013-08-25 12:17:46 +02:00
} ,
2015-07-04 10:10:21 +02:00
onMessageSubmitted : function ( text ) {
/ * T h i s m e t h o d g e t s c a l l e d o n c e t h e u s e r h a s t y p e d a m e s s a g e
* and then pressed enter in a chat box .
*
* Parameters :
* ( string ) text - The chat message text .
* /
2015-06-07 18:29:36 +02:00
if ( ! converse . connection . authenticated ) {
return this . showHelpMessages ( [ 'Sorry, the connection has been lost, and your message could not be sent' ] , 'error' ) ;
}
2013-08-16 16:05:24 +02:00
var match = text . replace ( /^\s*/ , "" ) . match ( /^\/(.*)\s*$/ ) , msgs ;
2013-06-02 00:21:06 +02:00
if ( match ) {
if ( match [ 1 ] === "clear" ) {
2014-04-24 18:03:30 +02:00
return this . clearMessages ( ) ;
2013-06-02 00:21:06 +02:00
}
else if ( match [ 1 ] === "help" ) {
msgs = [
'<strong>/help</strong>:' + _ _ ( 'Show this menu' ) + '' ,
'<strong>/me</strong>:' + _ _ ( 'Write in the third person' ) + '' ,
'<strong>/clear</strong>:' + _ _ ( 'Remove messages' ) + ''
] ;
2013-08-25 12:06:53 +02:00
this . showHelpMessages ( msgs ) ;
2013-06-02 00:21:06 +02:00
return ;
2014-03-05 00:23:45 +01:00
} else if ( ( converse . allow _otr ) && ( match [ 1 ] === "endotr" ) ) {
2013-09-08 15:54:04 +02:00
return this . endOTR ( ) ;
2014-03-05 00:23:45 +01:00
} else if ( ( converse . allow _otr ) && ( match [ 1 ] === "otr" ) ) {
2013-09-08 16:55:40 +02:00
return this . model . initiateOTR ( ) ;
2013-08-16 15:48:23 +02:00
}
2012-09-21 16:04:57 +02:00
}
2013-08-31 17:43:38 +02:00
if ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . model . get ( 'otr_status' ) ) ) {
2013-08-25 12:17:46 +02:00
// Off-the-record encryption is active
2013-08-16 16:05:24 +02:00
this . model . otr . sendMsg ( text ) ;
2013-08-25 12:17:46 +02:00
this . model . trigger ( 'showSentOTRMessage' , text ) ;
2013-08-31 17:43:38 +02:00
} else {
2013-08-24 23:09:54 +02:00
// We only save unencrypted messages.
2013-11-04 14:57:22 +01:00
var fullname = converse . xmppstatus . get ( 'fullname' ) ;
fullname = _ . isEmpty ( fullname ) ? converse . bare _jid : fullname ;
2015-09-30 14:18:56 +02:00
var message = this . model . messages . create ( {
2013-11-04 14:57:22 +01:00
fullname : fullname ,
2013-08-24 23:09:54 +02:00
sender : 'me' ,
2014-04-25 22:56:59 +02:00
time : moment ( ) . format ( ) ,
2013-08-24 23:09:54 +02:00
message : text
} ) ;
2015-09-30 14:18:56 +02:00
this . sendMessage ( message ) ;
2013-08-16 16:05:24 +02:00
}
} ,
2015-01-01 21:25:12 +01:00
sendChatState : function ( ) {
2015-01-09 09:02:35 +01:00
/ * S e n d s a m e s s a g e w i t h t h e s t a t u s o f t h e u s e r i n t h i s c h a t s e s s i o n
2015-01-01 21:25:12 +01:00
* as taken from the 'chat_state' attribute of the chat box .
2015-01-09 09:02:35 +01:00
* See XEP - 0085 Chat State Notifications .
2015-01-01 21:25:12 +01:00
* /
converse . connection . send (
$msg ( { 'to' : this . model . get ( 'jid' ) , 'type' : 'chat' } )
. c ( this . model . get ( 'chat_state' ) , { 'xmlns' : Strophe . NS . CHATSTATES } )
) ;
} ,
2015-01-09 09:02:35 +01:00
setChatState : function ( state , no _save ) {
/ * M u t a t o r f o r s e t t i n g t h e c h a t s t a t e o f t h i s c h a t s e s s i o n .
* Handles clearing of any chat state notification timeouts and
* setting new ones if necessary .
* Timeouts are set when the state being set is COMPOSING or PAUSED .
* After the timeout , COMPOSING will become PAUSED and PAUSED will become INACTIVE .
* See XEP - 0085 Chat State Notifications .
*
* Parameters :
* ( string ) state - The chat state ( consts ACTIVE , COMPOSING , PAUSED , INACTIVE , GONE )
2015-07-19 23:23:02 +02:00
* ( Boolean ) no _save - Just do the cleanup or setup but don ' t actually save the state .
2015-01-01 21:25:12 +01:00
* /
2015-04-03 09:47:31 +02:00
if ( typeof this . chat _state _timeout !== 'undefined' ) {
clearTimeout ( this . chat _state _timeout ) ;
delete this . chat _state _timeout ;
}
if ( state === COMPOSING ) {
2015-01-09 09:02:35 +01:00
this . chat _state _timeout = setTimeout (
2015-06-27 06:36:25 +02:00
this . setChatState . bind ( this ) , converse . TIMEOUTS . PAUSED , PAUSED ) ;
2015-01-09 09:02:35 +01:00
} else if ( state === PAUSED ) {
this . chat _state _timeout = setTimeout (
2015-06-27 06:36:25 +02:00
this . setChatState . bind ( this ) , converse . TIMEOUTS . INACTIVE , INACTIVE ) ;
2015-01-09 09:02:35 +01:00
}
2015-10-25 18:49:35 +01:00
if ( ! no _save && this . model . get ( 'chat_state' ) !== state ) {
2015-01-09 09:02:35 +01:00
this . model . set ( 'chat_state' , state ) ;
2015-01-01 21:25:12 +01:00
}
2015-01-09 09:02:35 +01:00
return this ;
2015-01-01 21:25:12 +01:00
} ,
2013-06-02 00:21:06 +02:00
keyPressed : function ( ev ) {
2015-01-09 09:02:35 +01:00
/ * E v e n t h a n d l e r f o r w h e n a k e y i s p r e s s e d i n a c h a t b o x t e x t a r e a .
* /
2015-01-01 21:25:12 +01:00
var $textarea = $ ( ev . target ) , message ;
2015-10-25 18:49:35 +01:00
if ( ev . keyCode === KEY . ENTER ) {
2013-06-02 00:21:06 +02:00
ev . preventDefault ( ) ;
message = $textarea . val ( ) ;
$textarea . val ( '' ) . focus ( ) ;
if ( message !== '' ) {
if ( this . model . get ( 'chatroom' ) ) {
2015-07-04 10:10:21 +02:00
this . onChatRoomMessageSubmitted ( message ) ;
2013-06-02 00:21:06 +02:00
} else {
2015-07-04 10:10:21 +02:00
this . onMessageSubmitted ( message ) ;
2013-06-02 00:21:06 +02:00
}
2014-07-06 18:31:17 +02:00
converse . emit ( 'messageSend' , message ) ;
2013-06-02 00:21:06 +02:00
}
2015-01-09 09:02:35 +01:00
this . setChatState ( ACTIVE ) ;
} else if ( ! this . model . get ( 'chatroom' ) ) { // chat state data is currently only for single user chat
// Set chat state to composing if keyCode is not a forward-slash
// (which would imply an internal command and not a message).
2015-10-25 18:49:35 +01:00
this . setChatState ( COMPOSING , ev . keyCode === KEY . FORWARD _SLASH ) ;
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
2015-09-24 19:41:35 +02:00
onStartVerticalResize : function ( ev ) {
2014-02-22 22:20:36 +01:00
if ( ! converse . allow _dragresize ) { return true ; }
// Record element attributes for mouseMove().
2014-02-23 02:38:26 +01:00
this . height = this . $el . children ( '.box-flyout' ) . height ( ) ;
2015-09-24 19:41:35 +02:00
converse . resizing = {
'chatbox' : this ,
'direction' : 'top'
} ;
2014-02-23 02:38:26 +01:00
this . prev _pageY = ev . pageY ;
2014-02-22 22:20:36 +01:00
} ,
2015-09-24 19:41:35 +02:00
onStartHorizontalResize : function ( ev ) {
if ( ! converse . allow _dragresize ) { return true ; }
this . width = this . $el . children ( '.box-flyout' ) . width ( ) ;
converse . resizing = {
'chatbox' : this ,
'direction' : 'left'
} ;
this . prev _pageX = ev . pageX ;
} ,
onStartDiagonalResize : function ( ev ) {
this . onStartHorizontalResize ( ev ) ;
this . onStartVerticalResize ( ev ) ;
converse . resizing . direction = 'topleft' ;
} ,
2014-02-28 13:22:15 +01:00
setChatBoxHeight : function ( height ) {
2014-02-28 19:55:46 +01:00
if ( ! this . model . get ( 'minimized' ) ) {
2015-11-05 11:02:23 +01:00
if ( height ) {
height = converse . applyDragResistance ( height , this . model . get ( 'default_height' ) ) + 'px' ;
} else {
height = "" ;
}
2015-09-24 19:41:35 +02:00
this . $el . children ( '.box-flyout' ) [ 0 ] . style . height = height ;
}
} ,
setChatBoxWidth : function ( width ) {
if ( ! this . model . get ( 'minimized' ) ) {
2015-11-05 11:02:23 +01:00
if ( width ) {
width = converse . applyDragResistance ( width , this . model . get ( 'default_width' ) ) + 'px' ;
} else {
width = "" ;
}
2015-09-24 19:41:35 +02:00
this . $el [ 0 ] . style . width = width ;
this . $el . children ( '.box-flyout' ) [ 0 ] . style . width = width ;
2014-02-28 19:55:46 +01:00
}
2014-02-28 13:22:15 +01:00
} ,
resizeChatBox : function ( ev ) {
2015-09-24 19:41:35 +02:00
var diff ;
if ( converse . resizing . direction . indexOf ( 'top' ) === 0 ) {
diff = ev . pageY - this . prev _pageY ;
if ( diff ) {
2015-09-25 23:36:28 +02:00
this . height = ( ( this . height - diff ) > ( this . model . get ( 'min_height' ) || 0 ) ) ? ( this . height - diff ) : this . model . get ( 'min_height' ) ;
2015-09-24 19:41:35 +02:00
this . prev _pageY = ev . pageY ;
this . setChatBoxHeight ( this . height ) ;
}
}
if ( converse . resizing . direction . indexOf ( 'left' ) !== - 1 ) {
diff = this . prev _pageX - ev . pageX ;
if ( diff ) {
2015-09-25 23:36:28 +02:00
this . width = ( ( this . width + diff ) > ( this . model . get ( 'min_width' ) || 0 ) ) ? ( this . width + diff ) : this . model . get ( 'min_width' ) ;
2015-09-24 19:41:35 +02:00
this . prev _pageX = ev . pageX ;
this . setChatBoxWidth ( this . width ) ;
}
}
2014-02-22 22:20:36 +01:00
} ,
2014-04-24 18:03:30 +02:00
clearMessages : function ( ev ) {
2014-07-14 20:41:26 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-04-24 18:03:30 +02:00
var result = confirm ( _ _ ( "Are you sure you want to clear the messages from this chat box?" ) ) ;
if ( result === true ) {
2015-07-19 12:08:43 +02:00
this . $content . empty ( ) ;
2014-04-24 18:03:30 +02:00
this . model . messages . reset ( ) ;
2014-06-30 18:53:58 +02:00
this . model . messages . browserStorage . _clear ( ) ;
2014-04-24 18:03:30 +02:00
}
return this ;
} ,
2013-11-03 21:28:44 +01:00
insertEmoticon : function ( ev ) {
2013-10-17 20:30:37 +02:00
ev . stopPropagation ( ) ;
this . $el . find ( '.toggle-smiley ul' ) . slideToggle ( 200 ) ;
var $textbox = this . $el . find ( 'textarea.chat-textarea' ) ;
var value = $textbox . val ( ) ;
var $target = $ ( ev . target ) ;
$target = $target . is ( 'a' ) ? $target : $target . children ( 'a' ) ;
if ( value && ( value [ value . length - 1 ] !== ' ' ) ) {
value = value + ' ' ;
}
2013-10-20 18:13:34 +02:00
$textbox . focus ( ) . val ( value + $target . data ( 'emoticon' ) + ' ' ) ;
2013-10-17 20:30:37 +02:00
} ,
2013-10-20 18:13:34 +02:00
toggleEmoticonMenu : function ( ev ) {
2013-10-17 20:30:37 +02:00
ev . stopPropagation ( ) ;
this . $el . find ( '.toggle-smiley ul' ) . slideToggle ( 200 ) ;
} ,
2013-08-25 22:10:32 +02:00
toggleOTRMenu : function ( ev ) {
ev . stopPropagation ( ) ;
2013-10-17 20:30:37 +02:00
this . $el . find ( '.toggle-otr ul' ) . slideToggle ( 200 ) ;
2013-08-25 22:10:32 +02:00
} ,
2013-08-31 15:45:23 +02:00
showOTRError : function ( msg ) {
2015-10-25 18:49:35 +01:00
if ( msg === 'Message cannot be sent at this time.' ) {
2013-08-31 15:45:23 +02:00
this . showHelpMessages (
[ _ _ ( 'Your message could not be sent' ) ] , 'error' ) ;
2015-10-25 18:49:35 +01:00
} else if ( msg === 'Received an unencrypted message.' ) {
2013-08-31 15:45:23 +02:00
this . showHelpMessages (
[ _ _ ( 'We received an unencrypted message' ) ] , 'error' ) ;
2015-10-25 18:49:35 +01:00
} else if ( msg === 'Received an unreadable encrypted message.' ) {
2013-08-31 15:45:23 +02:00
this . showHelpMessages (
[ _ _ ( 'We received an unreadable encrypted message' ) ] ,
'error' ) ;
} else {
this . showHelpMessages ( [ 'Encryption error occured: ' + msg ] , 'error' ) ;
}
2015-10-25 18:49:35 +01:00
converse . log ( "OTR ERROR:" + msg ) ;
2013-08-31 15:45:23 +02:00
} ,
2013-08-30 21:18:46 +02:00
startOTRFromToolbar : function ( ev ) {
$ ( ev . target ) . parent ( ) . parent ( ) . slideUp ( ) ;
ev . stopPropagation ( ) ;
2013-08-31 23:42:48 +02:00
this . model . initiateOTR ( ) ;
2013-08-30 21:18:46 +02:00
} ,
2013-08-25 22:10:32 +02:00
endOTR : function ( ev ) {
2013-11-11 20:03:41 +01:00
if ( typeof ev !== "undefined" ) {
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
}
2013-09-08 15:54:04 +02:00
this . model . endOTR ( ) ;
2013-08-25 22:10:32 +02:00
} ,
authOTR : function ( ev ) {
2013-08-31 22:28:33 +02:00
var scheme = $ ( ev . target ) . data ( ) . scheme ;
var result , question , answer ;
if ( scheme === 'fingerprint' ) {
2013-09-10 22:52:15 +02:00
result = confirm ( _ _ ( 'Here are the fingerprints, please confirm them with %1$s, outside of this chat.\n\nFingerprint for you, %2$s: %3$s\n\nFingerprint for %1$s: %4$s\n\nIf you have confirmed that the fingerprints match, click OK, otherwise click Cancel.' , [
2013-08-31 22:28:33 +02:00
this . model . get ( 'fullname' ) ,
converse . xmppstatus . get ( 'fullname' ) || converse . bare _jid ,
this . model . otr . priv . fingerprint ( ) ,
this . model . otr . their _priv _pk . fingerprint ( )
]
) ) ;
if ( result === true ) {
this . model . save ( { 'otr_status' : VERIFIED } ) ;
} else {
this . model . save ( { 'otr_status' : UNVERIFIED } ) ;
}
} else if ( scheme === 'smp' ) {
2014-11-09 18:07:06 +01:00
alert ( _ _ ( 'You will be prompted to provide a security question and then an answer to that question.\n\nYour contact will then be prompted the same question and if they type the exact same answer (case sensitive), their identity will be verified.' ) ) ;
2013-08-31 22:28:33 +02:00
question = prompt ( _ _ ( 'What is your security question?' ) ) ;
2013-08-31 23:20:07 +02:00
if ( question ) {
answer = prompt ( _ _ ( 'What is the answer to the security question?' ) ) ;
this . model . otr . smpSecret ( answer , question ) ;
}
2013-08-31 17:27:14 +02:00
} else {
2013-08-31 22:28:33 +02:00
this . showHelpMessages ( [ _ _ ( 'Invalid authentication scheme provided' ) ] , 'error' ) ;
2013-08-31 17:27:14 +02:00
}
2013-08-25 22:10:32 +02:00
} ,
2013-12-18 03:52:16 +01:00
toggleCall : function ( ev ) {
ev . stopPropagation ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'callButtonClicked' , {
2013-12-26 02:41:20 +01:00
connection : converse . connection ,
model : this . model
2013-12-18 03:52:16 +01:00
} ) ;
} ,
2015-01-01 21:25:12 +01:00
onChatStatusChanged : function ( item ) {
var chat _status = item . get ( 'chat_status' ) ,
fullname = item . get ( 'fullname' ) ;
fullname = _ . isEmpty ( fullname ) ? item . get ( 'jid' ) : fullname ;
if ( this . $el . is ( ':visible' ) ) {
if ( chat _status === 'offline' ) {
2015-01-09 10:48:36 +01:00
this . showStatusNotification ( fullname + ' ' + _ _ ( 'has gone offline' ) ) ;
2015-01-01 21:25:12 +01:00
} else if ( chat _status === 'away' ) {
2015-01-09 10:48:36 +01:00
this . showStatusNotification ( fullname + ' ' + _ _ ( 'has gone away' ) ) ;
2015-01-01 21:25:12 +01:00
} else if ( ( chat _status === 'dnd' ) ) {
2015-01-09 10:48:36 +01:00
this . showStatusNotification ( fullname + ' ' + _ _ ( 'is busy' ) ) ;
2015-01-01 21:25:12 +01:00
} else if ( chat _status === 'online' ) {
this . $el . find ( 'div.chat-event' ) . remove ( ) ;
2013-03-24 10:48:12 +01:00
}
2013-10-20 22:20:45 +02:00
}
2015-01-01 21:25:12 +01:00
converse . emit ( 'contactStatusChanged' , item . attributes , item . get ( 'chat_status' ) ) ;
} ,
onStatusChanged : function ( item ) {
this . showStatusMessage ( ) ;
converse . emit ( 'contactStatusMessageChanged' , item . attributes , item . get ( 'status' ) ) ;
} ,
2015-10-25 18:49:35 +01:00
onOTRStatusChanged : function ( ) {
2015-01-01 21:25:12 +01:00
this . renderToolbar ( ) . informOTRChange ( ) ;
} ,
onMinimizedChanged : function ( item ) {
if ( item . get ( 'minimized' ) ) {
this . hide ( ) ;
} else {
this . maximize ( ) ;
2014-05-27 18:34:22 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
showStatusMessage : function ( msg ) {
2014-06-01 20:09:09 +02:00
msg = msg || this . model . get ( 'status' ) ;
2014-11-15 13:28:11 +01:00
if ( typeof msg === "string" ) {
2014-06-01 20:09:09 +02:00
this . $el . find ( 'p.user-custom-message' ) . text ( msg ) . attr ( 'title' , msg ) ;
}
2014-06-02 04:47:23 +02:00
return this ;
2013-06-02 00:21:06 +02:00
} ,
2014-07-06 12:40:50 +02:00
close : function ( ev ) {
2014-09-22 12:55:14 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-09-06 23:34:39 +02:00
if ( converse . connection . connected ) {
2013-06-02 00:21:06 +02:00
this . model . destroy ( ) ;
2015-03-04 22:27:11 +01:00
this . setChatState ( INACTIVE ) ;
2013-06-02 00:21:06 +02:00
} else {
2015-03-04 22:27:11 +01:00
this . hide ( ) ;
2013-06-02 00:21:06 +02:00
}
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxClosed' , this ) ;
2014-04-24 19:14:37 +02:00
return this ;
2013-06-02 00:21:06 +02:00
} ,
2014-06-01 20:09:09 +02:00
maximize : function ( ) {
2015-05-31 14:50:11 +02:00
var chatboxviews = converse . chatboxviews ;
2014-06-08 21:43:00 +02:00
// Restores a minimized chat box
2015-06-27 06:36:25 +02:00
this . $el . insertAfter ( chatboxviews . get ( "controlbox" ) . $el ) . show ( 'fast' , function ( ) {
2015-05-31 14:50:11 +02:00
/ * N o w t h a t t h e c h a t b o x i s v i s i b l e , w e c a n c a l l t r i m C h a t s
* to make space available if need be .
* /
chatboxviews . trimChats ( this ) ;
2014-06-08 21:43:00 +02:00
converse . refreshWebkit ( ) ;
2015-01-09 09:02:35 +01:00
this . setChatState ( ACTIVE ) . focus ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxMaximized' , this ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-01-22 22:50:39 +01:00
} ,
2014-06-02 04:47:23 +02:00
minimize : function ( ev ) {
2014-09-17 10:35:24 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-06-08 21:43:00 +02:00
// Minimizes a chat box
2015-01-09 09:02:35 +01:00
this . setChatState ( INACTIVE ) . model . minimize ( ) ;
2014-06-01 20:09:09 +02:00
this . $el . hide ( 'fast' , converse . refreshwebkit ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxMinimized' , this ) ;
2014-01-22 22:50:39 +01:00
} ,
2013-06-02 00:21:06 +02:00
updateVCard : function ( ) {
2015-05-03 17:43:22 +02:00
if ( ! this . use _vcards ) { return this ; }
2013-06-02 00:21:06 +02:00
var jid = this . model . get ( 'jid' ) ,
2014-08-03 23:02:25 +02:00
contact = converse . roster . get ( jid ) ;
2014-08-02 11:35:03 +02:00
if ( ( contact ) && ( ! contact . get ( 'vcard_updated' ) ) ) {
2013-06-02 00:21:06 +02:00
converse . getVCard (
jid ,
2015-06-27 06:36:25 +02:00
function ( iq , jid , fullname , image , image _type , url ) {
2013-06-02 00:21:06 +02:00
this . model . save ( {
'fullname' : fullname || jid ,
'url' : url ,
'image_type' : image _type ,
2013-10-03 14:22:33 +02:00
'image' : image
2013-06-02 00:21:06 +02:00
} ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ,
2015-04-06 13:41:48 +02:00
function ( ) {
2013-08-22 21:43:34 +02:00
converse . log ( "ChatBoxView.initialize: An error occured while fetching vcard" ) ;
2015-04-06 13:41:48 +02:00
}
2013-06-02 00:21:06 +02:00
) ;
}
2015-05-03 17:43:22 +02:00
return this ;
2013-06-02 00:21:06 +02:00
} ,
2013-08-31 15:45:23 +02:00
informOTRChange : function ( ) {
var data = this . model . toJSON ( ) ;
var msgs = [ ] ;
2015-10-25 18:49:35 +01:00
if ( data . otr _status === UNENCRYPTED ) {
2013-08-31 15:45:23 +02:00
msgs . push ( _ _ ( "Your messages are not encrypted anymore" ) ) ;
2015-10-25 18:49:35 +01:00
} else if ( data . otr _status === UNVERIFIED ) {
2014-11-09 18:07:06 +01:00
msgs . push ( _ _ ( "Your messages are now encrypted but your contact's identity has not been verified." ) ) ;
2015-10-25 18:49:35 +01:00
} else if ( data . otr _status === VERIFIED ) {
2014-11-09 18:07:06 +01:00
msgs . push ( _ _ ( "Your contact's identify has been verified." ) ) ;
2015-10-25 18:49:35 +01:00
} else if ( data . otr _status === FINISHED ) {
2014-11-09 18:07:06 +01:00
msgs . push ( _ _ ( "Your contact has ended encryption on their end, you should do the same." ) ) ;
2013-08-31 15:45:23 +02:00
}
2014-02-12 10:30:05 +01:00
return this . showHelpMessages ( msgs , 'info' , false ) ;
2013-08-31 15:45:23 +02:00
} ,
2013-08-30 22:49:33 +02:00
renderToolbar : function ( ) {
2013-09-08 16:55:40 +02:00
if ( converse . show _toolbar ) {
var data = this . model . toJSON ( ) ;
2015-10-25 18:49:35 +01:00
if ( data . otr _status === UNENCRYPTED ) {
2013-09-08 16:55:40 +02:00
data . otr _tooltip = _ _ ( 'Your messages are not encrypted. Click here to enable OTR encryption.' ) ;
2015-10-25 18:49:35 +01:00
} else if ( data . otr _status === UNVERIFIED ) {
2014-11-09 18:07:06 +01:00
data . otr _tooltip = _ _ ( 'Your messages are encrypted, but your contact has not been verified.' ) ;
2015-10-25 18:49:35 +01:00
} else if ( data . otr _status === VERIFIED ) {
2014-11-09 18:07:06 +01:00
data . otr _tooltip = _ _ ( 'Your messages are encrypted and your contact verified.' ) ;
2015-10-25 18:49:35 +01:00
} else if ( data . otr _status === FINISHED ) {
2014-11-09 18:07:06 +01:00
data . otr _tooltip = _ _ ( 'Your contact has closed their end of the private session, you should do the same' ) ;
2013-09-08 16:55:40 +02:00
}
2013-12-30 20:27:57 +01:00
this . $el . find ( '.chat-toolbar' ) . html (
converse . templates . toolbar (
_ . extend ( data , {
FINISHED : FINISHED ,
UNENCRYPTED : UNENCRYPTED ,
UNVERIFIED : UNVERIFIED ,
2014-02-28 03:04:52 +01:00
VERIFIED : VERIFIED ,
2013-12-30 20:27:57 +01:00
allow _otr : converse . allow _otr && ! this . is _chatroom ,
2014-09-03 20:04:32 +02:00
label _clear : _ _ ( 'Clear all messages' ) ,
2013-12-30 20:27:57 +01:00
label _end _encrypted _conversation : _ _ ( 'End encrypted conversation' ) ,
2015-05-15 12:01:29 +02:00
label _insert _smiley : _ _ ( 'Insert a smiley' ) ,
2015-10-31 17:30:06 +01:00
label _hide _occupants : _ _ ( 'Hide the list of occupants' ) ,
2013-12-30 20:27:57 +01:00
label _refresh _encrypted _conversation : _ _ ( 'Refresh encrypted conversation' ) ,
2014-09-03 20:04:32 +02:00
label _start _call : _ _ ( 'Start a call' ) ,
2013-12-30 20:27:57 +01:00
label _start _encrypted _conversation : _ _ ( 'Start encrypted conversation' ) ,
label _verify _with _fingerprints : _ _ ( 'Verify with fingerprints' ) ,
label _verify _with _smp : _ _ ( 'Verify with SMP' ) ,
label _whats _this : _ _ ( "What\'s this?" ) ,
otr _status _class : OTR _CLASS _MAPPING [ data . otr _status ] ,
otr _translated _status : OTR _TRANSLATED _MAPPING [ data . otr _status ] ,
2014-04-25 22:56:59 +02:00
show _call _button : converse . visible _toolbar _buttons . call ,
show _clear _button : converse . visible _toolbar _buttons . clear ,
2014-09-03 20:04:32 +02:00
show _emoticons : converse . visible _toolbar _buttons . emoticons ,
2015-10-31 17:30:06 +01:00
show _occupants _toggle : this . is _chatroom && converse . visible _toolbar _buttons . toggle _occupants
2013-12-30 20:27:57 +01:00
} )
)
) ;
2013-08-30 22:49:33 +02:00
}
return this ;
} ,
2013-06-02 00:21:06 +02:00
renderAvatar : function ( ) {
if ( ! this . model . get ( 'image' ) ) {
return ;
2013-03-24 10:48:12 +01:00
}
2013-06-02 00:21:06 +02:00
var img _src = 'data:' + this . model . get ( 'image_type' ) + ';base64,' + this . model . get ( 'image' ) ,
2014-12-07 13:44:27 +01:00
canvas = $ ( '<canvas height="32px" width="32px" class="avatar"></canvas>' ) . get ( 0 ) ;
2013-11-15 21:27:24 +01:00
if ( ! ( canvas . getContext && canvas . getContext ( '2d' ) ) ) {
return this ;
}
var ctx = canvas . getContext ( '2d' ) ;
var img = new Image ( ) ; // Create new Image object
2014-10-10 11:05:16 +02:00
img . onload = function ( ) {
2013-06-02 00:21:06 +02:00
var ratio = img . width / img . height ;
2015-10-30 18:10:09 +01:00
if ( ratio < 1 ) {
ctx . drawImage ( img , 0 , 0 , 32 , 32 * ( 1 / ratio ) ) ;
} else {
ctx . drawImage ( img , 0 , 0 , 32 , 32 * ratio ) ;
}
2013-06-02 00:21:06 +02:00
} ;
img . src = img _src ;
this . $el . find ( '.chat-title' ) . before ( canvas ) ;
return this ;
} ,
2012-07-11 16:16:17 +02:00
2013-06-02 00:21:06 +02:00
focus : function ( ) {
this . $el . find ( '.chat-textarea' ) . focus ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxFocused' , this ) ;
2013-06-02 00:21:06 +02:00
return this ;
} ,
2013-03-25 12:08:27 +01:00
2013-06-02 00:21:06 +02:00
hide : function ( ) {
2015-10-25 18:49:35 +01:00
if ( this . $el . is ( ':visible' ) && this . $el . css ( 'opacity' ) === "1" ) {
2014-06-02 04:47:23 +02:00
this . $el . hide ( ) ;
converse . refreshWebkit ( ) ;
2013-06-02 00:21:06 +02:00
}
2014-04-24 19:39:03 +02:00
return this ;
2013-06-02 00:21:06 +02:00
} ,
2014-01-22 22:19:45 +01:00
show : function ( callback ) {
2015-10-25 18:49:35 +01:00
if ( this . $el . is ( ':visible' ) && this . $el . css ( 'opacity' ) === "1" ) {
2014-04-24 19:39:03 +02:00
return this . focus ( ) ;
2013-06-02 00:21:06 +02:00
}
2015-11-05 11:02:23 +01:00
this . initDragResize ( ) . setDimensions ( ) ;
2015-07-17 23:46:02 +02:00
this . $el . fadeIn ( function ( ) {
2015-10-25 18:49:35 +01:00
if ( typeof callback === "function" ) {
2015-07-17 23:46:02 +02:00
callback . apply ( this , arguments ) ;
}
if ( converse . connection . connected ) {
2015-09-24 19:41:35 +02:00
// Without a connection, we haven't yet initialized
// localstorage
2015-07-17 23:46:02 +02:00
this . model . save ( ) ;
}
this . setChatState ( ACTIVE ) ;
this . scrollDown ( ) . focus ( ) ;
2015-07-20 09:54:56 +02:00
} . bind ( this ) ) ;
2015-07-17 23:46:02 +02:00
return this ;
2013-06-02 00:21:06 +02:00
} ,
2015-07-18 09:20:13 +02:00
scrollDownMessageHeight : function ( $message ) {
2015-07-19 12:08:43 +02:00
if ( this . $content . is ( ':visible' ) ) {
this . $content . scrollTop ( this . $content . scrollTop ( ) + $message [ 0 ] . scrollHeight ) ;
2015-07-18 09:20:13 +02:00
}
return this ;
} ,
2013-06-02 00:21:06 +02:00
scrollDown : function ( ) {
2015-07-19 12:08:43 +02:00
if ( this . $content . is ( ':visible' ) ) {
this . $content . scrollTop ( this . $content [ 0 ] . scrollHeight ) ;
2014-06-02 21:51:30 +02:00
}
2013-06-02 00:21:06 +02:00
return this ;
2013-04-01 19:53:39 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
this . ContactsPanel = Backbone . View . extend ( {
tagName : 'div' ,
2014-01-22 14:35:48 +01:00
className : 'controlbox-pane' ,
2013-06-02 00:21:06 +02:00
id : 'users' ,
events : {
'click a.toggle-xmpp-contact-form' : 'toggleContactForm' ,
'submit form.add-xmpp-contact' : 'addContactFromForm' ,
'submit form.search-xmpp-contact' : 'searchContacts' ,
'click a.subscribe-to-user' : 'addContactFromList'
} ,
2013-08-02 12:26:16 +02:00
initialize : function ( cfg ) {
cfg . $parent . append ( this . $el ) ;
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
} ,
2013-06-02 00:21:06 +02:00
render : function ( ) {
var markup ;
2013-12-30 20:27:57 +01:00
var widgets = converse . templates . contacts _panel ( {
label _online : _ _ ( 'Online' ) ,
label _busy : _ _ ( 'Busy' ) ,
label _away : _ _ ( 'Away' ) ,
2014-09-07 13:18:36 +02:00
label _offline : _ _ ( 'Offline' ) ,
label _logout : _ _ ( 'Log out' ) ,
2014-10-26 15:52:27 +01:00
allow _logout : converse . allow _logout
2013-12-30 20:27:57 +01:00
} ) ;
2014-08-11 21:47:51 +02:00
this . $tabs . append ( converse . templates . contacts _tab ( { label _contacts : LABEL _CONTACTS } ) ) ;
2013-06-02 00:21:06 +02:00
if ( converse . xhr _user _search ) {
2013-12-30 20:27:57 +01:00
markup = converse . templates . search _contact ( {
label _contact _name : _ _ ( 'Contact name' ) ,
label _search : _ _ ( 'Search' )
} ) ;
2013-06-02 00:21:06 +02:00
} else {
2013-12-30 20:27:57 +01:00
markup = converse . templates . add _contact _form ( {
2015-05-02 00:21:11 +02:00
label _contact _username : _ _ ( 'e.g. user@example.com' ) ,
2013-12-30 20:27:57 +01:00
label _add : _ _ ( 'Add' )
} ) ;
}
2013-10-03 14:22:33 +02:00
if ( converse . allow _contact _requests ) {
2013-12-30 20:27:57 +01:00
widgets += converse . templates . add _contact _dropdown ( {
label _click _to _chat : _ _ ( 'Click to add new chat contacts' ) ,
label _add _contact : _ _ ( 'Add a contact' )
} ) ;
2013-06-02 00:21:06 +02:00
}
2013-10-03 14:22:33 +02:00
this . $el . html ( widgets ) ;
2013-06-02 00:21:06 +02:00
this . $el . find ( '.search-xmpp ul' ) . append ( markup ) ;
return this ;
} ,
toggleContactForm : function ( ev ) {
ev . preventDefault ( ) ;
this . $el . find ( '.search-xmpp' ) . toggle ( 'fast' , function ( ) {
if ( $ ( this ) . is ( ':visible' ) ) {
$ ( this ) . find ( 'input.username' ) . focus ( ) ;
}
} ) ;
} ,
searchContacts : function ( ev ) {
ev . preventDefault ( ) ;
2014-07-11 16:12:11 +02:00
$ . getJSON ( converse . xhr _user _search _url + "?q=" + $ ( ev . target ) . find ( 'input.username' ) . val ( ) , function ( data ) {
2013-06-02 00:21:06 +02:00
var $ul = $ ( '.search-xmpp ul' ) ;
$ul . find ( 'li.found-user' ) . remove ( ) ;
$ul . find ( 'li.chat-info' ) . remove ( ) ;
if ( ! data . length ) {
$ul . append ( '<li class="chat-info">' + _ _ ( 'No users found' ) + '</li>' ) ;
}
$ ( data ) . each ( function ( idx , obj ) {
$ul . append (
$ ( '<li class="found-user"></li>' )
. append (
$ ( '<a class="subscribe-to-user" href="#" title="' + _ _ ( 'Click to add as a chat contact' ) + '"></a>' )
2015-04-24 19:16:25 +02:00
. attr ( 'data-recipient' , Strophe . getNodeFromJid ( obj . id ) + "@" + Strophe . getDomainFromJid ( obj . id ) )
2013-06-02 00:21:06 +02:00
. text ( obj . fullname )
)
) ;
} ) ;
} ) ;
} ,
2013-03-25 12:08:27 +01:00
2013-06-02 00:21:06 +02:00
addContactFromForm : function ( ev ) {
ev . preventDefault ( ) ;
var $input = $ ( ev . target ) . find ( 'input' ) ;
var jid = $input . val ( ) ;
if ( ! jid ) {
// this is not a valid JID
$input . addClass ( 'error' ) ;
return ;
}
2015-04-06 11:10:05 +02:00
converse . roster . addAndSubscribe ( jid ) ;
2013-06-02 00:21:06 +02:00
$ ( '.search-xmpp' ) . hide ( ) ;
} ,
2013-04-17 00:08:01 +02:00
2013-06-02 00:21:06 +02:00
addContactFromList : function ( ev ) {
ev . preventDefault ( ) ;
var $target = $ ( ev . target ) ,
jid = $target . attr ( 'data-recipient' ) ,
name = $target . text ( ) ;
2015-04-06 11:10:05 +02:00
converse . roster . addAndSubscribe ( jid , name ) ;
2013-06-02 00:21:06 +02:00
$target . parent ( ) . remove ( ) ;
$ ( '.search-xmpp' ) . hide ( ) ;
2013-03-22 16:43:00 +01:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2012-09-21 16:04:57 +02:00
2013-06-02 00:21:06 +02:00
this . RoomsPanel = Backbone . View . extend ( {
tagName : 'div' ,
2014-11-24 18:57:28 +01:00
className : 'controlbox-pane' ,
2013-06-02 00:21:06 +02:00
id : 'chatrooms' ,
events : {
'submit form.add-chatroom' : 'createChatRoom' ,
'click input#show-rooms' : 'showRooms' ,
'click a.open-room' : 'createChatRoom' ,
2014-11-15 13:15:02 +01:00
'click a.room-info' : 'showRoomInfo' ,
'change input[name=server]' : 'setDomain' ,
'change input[name=nick]' : 'setNick'
2013-06-02 00:21:06 +02:00
} ,
2013-08-02 12:26:16 +02:00
initialize : function ( cfg ) {
2014-11-15 13:15:02 +01:00
this . $parent = cfg . $parent ;
this . model . on ( 'change:muc_domain' , this . onDomainChange , this ) ;
this . model . on ( 'change:nick' , this . onNickChange , this ) ;
} ,
render : function ( ) {
this . $parent . append (
2013-06-02 00:21:06 +02:00
this . $el . html (
2014-01-19 05:21:49 +01:00
converse . templates . room _panel ( {
'server_input_type' : converse . hide _muc _server && 'hidden' || 'text' ,
2015-03-17 11:27:43 +01:00
'server_label_global_attr' : converse . hide _muc _server && ' hidden' || '' ,
2014-01-19 05:21:49 +01:00
'label_room_name' : _ _ ( 'Room name' ) ,
'label_nickname' : _ _ ( 'Nickname' ) ,
'label_server' : _ _ ( 'Server' ) ,
2014-12-13 00:14:35 +01:00
'label_join' : _ _ ( 'Join Room' ) ,
2014-01-19 05:21:49 +01:00
'label_show_rooms' : _ _ ( 'Show rooms' )
2013-06-02 00:21:06 +02:00
} )
) . hide ( ) ) ;
2014-11-15 13:15:02 +01:00
this . $tabs = this . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
this . $tabs . append ( converse . templates . chatrooms _tab ( { label _rooms : _ _ ( 'Rooms' ) } ) ) ;
return this ;
} ,
2012-10-25 23:33:09 +02:00
2014-11-15 13:15:02 +01:00
onDomainChange : function ( model ) {
var $server = this . $el . find ( 'input.new-chatroom-server' ) ;
$server . val ( model . get ( 'muc_domain' ) ) ;
if ( converse . auto _list _rooms ) {
2013-06-02 00:21:06 +02:00
this . updateRoomsList ( ) ;
2014-11-15 13:15:02 +01:00
}
2013-06-02 00:21:06 +02:00
} ,
2014-11-15 13:15:02 +01:00
onNickChange : function ( model ) {
var $nick = this . $el . find ( 'input.new-chatroom-nick' ) ;
$nick . val ( model . get ( 'nick' ) ) ;
2013-08-02 12:26:16 +02:00
} ,
2013-06-02 00:21:06 +02:00
informNoRoomsFound : function ( ) {
var $available _chatrooms = this . $el . find ( '#available-chatrooms' ) ;
// # For translators: %1$s is a variable and will be replaced with the XMPP server name
2014-11-15 13:15:02 +01:00
$available _chatrooms . html ( '<dt>' + _ _ ( 'No rooms on %1$s' , this . model . get ( 'muc_domain' ) ) + '</dt>' ) ;
2013-06-02 00:21:06 +02:00
$ ( 'input#show-rooms' ) . show ( ) . siblings ( 'span.spinner' ) . remove ( ) ;
} ,
2015-03-01 19:09:10 +01:00
onRoomsFound : function ( iq ) {
/ * H a n d l e t h e I Q s t a n z a r e t u r n e d f r o m t h e s e r v e r , c o n t a i n i n g
* all its public rooms .
* /
var name , jid , i , fragment ,
$available _chatrooms = this . $el . find ( '#available-chatrooms' ) ;
this . rooms = $ ( iq ) . find ( 'query' ) . find ( 'item' ) ;
if ( this . rooms . length ) {
// # For translators: %1$s is a variable and will be
// # replaced with the XMPP server name
$available _chatrooms . html ( '<dt>' + _ _ ( 'Rooms on %1$s' , this . model . get ( 'muc_domain' ) ) + '</dt>' ) ;
fragment = document . createDocumentFragment ( ) ;
for ( i = 0 ; i < this . rooms . length ; i ++ ) {
name = Strophe . unescapeNode ( $ ( this . rooms [ i ] ) . attr ( 'name' ) || $ ( this . rooms [ i ] ) . attr ( 'jid' ) ) ;
jid = $ ( this . rooms [ i ] ) . attr ( 'jid' ) ;
fragment . appendChild ( $ (
converse . templates . room _item ( {
'name' : name ,
'jid' : jid ,
'open_title' : _ _ ( 'Click to open this room' ) ,
'info_title' : _ _ ( 'Show more information on this room' )
} )
) [ 0 ] ) ;
}
$available _chatrooms . append ( fragment ) ;
$ ( 'input#show-rooms' ) . show ( ) . siblings ( 'span.spinner' ) . remove ( ) ;
} else {
this . informNoRoomsFound ( ) ;
}
return true ;
} ,
2014-11-15 13:15:02 +01:00
updateRoomsList : function ( ) {
2015-03-01 13:32:53 +01:00
/ * S e n d a n d I Q s t a n z a t o t h e s e r v e r a s k i n g f o r a l l r o o m s
* /
converse . connection . sendIQ (
$iq ( {
to : this . model . get ( 'muc_domain' ) ,
from : converse . connection . jid ,
type : "get"
} ) . c ( "query" , { xmlns : Strophe . NS . DISCO _ITEMS } ) ,
2015-03-05 20:18:25 +01:00
this . onRoomsFound . bind ( this ) ,
this . informNoRoomsFound . bind ( this )
2015-03-01 19:09:10 +01:00
) ;
2013-06-02 00:21:06 +02:00
} ,
2015-10-25 18:49:35 +01:00
showRooms : function ( ) {
2013-06-02 00:21:06 +02:00
var $available _chatrooms = this . $el . find ( '#available-chatrooms' ) ;
var $server = this . $el . find ( 'input.new-chatroom-server' ) ;
var server = $server . val ( ) ;
if ( ! server ) {
$server . addClass ( 'error' ) ;
2013-05-21 14:07:17 +02:00
return ;
}
2013-06-02 00:21:06 +02:00
this . $el . find ( 'input.new-chatroom-name' ) . removeClass ( 'error' ) ;
$server . removeClass ( 'error' ) ;
$available _chatrooms . empty ( ) ;
$ ( 'input#show-rooms' ) . hide ( ) . after ( '<span class="spinner"/>' ) ;
2014-11-15 13:15:02 +01:00
this . model . save ( { muc _domain : server } ) ;
2013-06-02 00:21:06 +02:00
this . updateRoomsList ( ) ;
} ,
showRoomInfo : function ( ev ) {
var target = ev . target ,
$dd = $ ( target ) . parent ( 'dd' ) ,
$div = $dd . find ( 'div.room-info' ) ;
if ( $div . length ) {
$div . remove ( ) ;
} else {
$dd . find ( 'span.spinner' ) . remove ( ) ;
$dd . append ( '<span class="spinner hor_centered"/>' ) ;
converse . connection . disco . info (
$ ( target ) . attr ( 'data-room-jid' ) ,
null ,
2015-06-27 06:36:25 +02:00
function ( stanza ) {
2013-06-02 00:21:06 +02:00
var $stanza = $ ( stanza ) ;
// All MUC features found here: http://xmpp.org/registrar/disco-features.html
$dd . find ( 'span.spinner' ) . replaceWith (
2014-01-19 05:21:49 +01:00
converse . templates . room _description ( {
2013-06-02 00:21:06 +02:00
'desc' : $stanza . find ( 'field[var="muc#roominfo_description"] value' ) . text ( ) ,
'occ' : $stanza . find ( 'field[var="muc#roominfo_occupants"] value' ) . text ( ) ,
'hidden' : $stanza . find ( 'feature[var="muc_hidden"]' ) . length ,
'membersonly' : $stanza . find ( 'feature[var="muc_membersonly"]' ) . length ,
'moderated' : $stanza . find ( 'feature[var="muc_moderated"]' ) . length ,
'nonanonymous' : $stanza . find ( 'feature[var="muc_nonanonymous"]' ) . length ,
'open' : $stanza . find ( 'feature[var="muc_open"]' ) . length ,
'passwordprotected' : $stanza . find ( 'feature[var="muc_passwordprotected"]' ) . length ,
'persistent' : $stanza . find ( 'feature[var="muc_persistent"]' ) . length ,
'publicroom' : $stanza . find ( 'feature[var="muc_public"]' ) . length ,
'semianonymous' : $stanza . find ( 'feature[var="muc_semianonymous"]' ) . length ,
'temporary' : $stanza . find ( 'feature[var="muc_temporary"]' ) . length ,
2014-03-09 14:56:35 +01:00
'unmoderated' : $stanza . find ( 'feature[var="muc_unmoderated"]' ) . length ,
2014-01-19 05:21:49 +01:00
'label_desc' : _ _ ( 'Description:' ) ,
'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' ) ,
2015-10-22 12:28:00 +02:00
'label_semi_anon' : _ _ ( 'Semi-anonymous' ) ,
'label_temp_room' : _ _ ( 'Temporary room' ) ,
2014-01-19 05:21:49 +01:00
'label_unmoderated' : _ _ ( 'Unmoderated' )
2013-06-02 00:21:06 +02:00
} ) ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2013-05-21 14:07:17 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
createChatRoom : function ( ev ) {
ev . preventDefault ( ) ;
var name , $name ,
server , $server ,
jid ,
$nick = this . $el . find ( 'input.new-chatroom-nick' ) ,
nick = $nick . val ( ) ,
chatroom ;
if ( ! nick ) { $nick . addClass ( 'error' ) ; }
else { $nick . removeClass ( 'error' ) ; }
if ( ev . type === 'click' ) {
2015-09-03 16:13:01 +02:00
name = $ ( ev . target ) . text ( ) ;
2013-06-02 00:21:06 +02:00
jid = $ ( ev . target ) . attr ( 'data-room-jid' ) ;
2012-09-21 16:04:57 +02:00
} else {
2013-06-02 00:21:06 +02:00
$name = this . $el . find ( 'input.new-chatroom-name' ) ;
$server = this . $el . find ( 'input.new-chatroom-server' ) ;
server = $server . val ( ) ;
2015-09-03 16:13:01 +02:00
name = $name . val ( ) . trim ( ) ;
2013-06-02 00:21:06 +02:00
$name . val ( '' ) ; // Clear the input
if ( name && server ) {
2015-09-03 16:13:01 +02:00
jid = Strophe . escapeNode ( name . toLowerCase ( ) ) + '@' + server ;
2013-06-02 00:21:06 +02:00
$name . removeClass ( 'error' ) ;
$server . removeClass ( 'error' ) ;
2014-11-15 13:15:02 +01:00
this . model . save ( { muc _domain : server } ) ;
2013-06-02 00:21:06 +02:00
} else {
if ( ! name ) { $name . addClass ( 'error' ) ; }
if ( ! server ) { $server . addClass ( 'error' ) ; }
return ;
}
}
if ( ! nick ) { return ; }
2014-05-27 18:34:22 +02:00
chatroom = converse . chatboxviews . showChat ( {
2013-06-02 00:21:06 +02:00
'id' : jid ,
'jid' : jid ,
2015-09-03 16:13:01 +02:00
'name' : name || Strophe . unescapeNode ( Strophe . getNodeFromJid ( jid ) ) ,
2013-06-02 00:21:06 +02:00
'nick' : nick ,
'chatroom' : true ,
2014-04-19 05:12:24 +02:00
'box_id' : b64 _sha1 ( jid )
2013-06-02 00:21:06 +02:00
} ) ;
2014-11-15 13:15:02 +01:00
} ,
setDomain : function ( ev ) {
this . model . save ( { muc _domain : ev . target . value } ) ;
} ,
setNick : function ( ev ) {
this . model . save ( { nick : ev . target . value } ) ;
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
this . ControlBoxView = converse . ChatBoxView . extend ( {
tagName : 'div' ,
className : 'chatbox' ,
id : 'controlbox' ,
events : {
2014-05-27 18:34:22 +02:00
'click a.close-chatbox-button' : 'close' ,
2014-02-28 13:22:15 +01:00
'click ul#controlbox-tabs li a' : 'switchTab' ,
2015-09-24 19:41:35 +02:00
'mousedown .dragresize-top' : 'onStartVerticalResize' ,
'mousedown .dragresize-left' : 'onStartHorizontalResize' ,
'mousedown .dragresize-topleft' : 'onStartDiagonalResize'
2013-06-02 00:21:06 +02:00
} ,
initialize : function ( ) {
2014-04-24 07:58:35 +02:00
this . $el . insertAfter ( converse . controlboxtoggle . $el ) ;
2015-11-05 11:02:23 +01:00
$ ( window ) . on ( 'resize' , _ . debounce ( this . setDimensions . bind ( this ) , 100 ) ) ;
2014-09-22 12:55:14 +02:00
this . model . on ( 'change:connected' , this . onConnected , this ) ;
2013-06-02 00:21:06 +02:00
this . model . on ( 'destroy' , this . hide , this ) ;
this . model . on ( 'hide' , this . hide , this ) ;
2014-09-22 12:55:14 +02:00
this . model . on ( 'show' , this . show , this ) ;
this . model . on ( 'change:closed' , this . ensureClosedState , this ) ;
this . render ( ) ;
if ( this . model . get ( 'connected' ) ) {
this . initRoster ( ) ;
}
if ( ! this . model . get ( 'closed' ) ) {
this . show ( ) ;
} else {
this . hide ( ) ;
}
} ,
2015-09-24 15:52:35 +02:00
render : function ( ) {
if ( ! converse . connection . connected || ! converse . connection . authenticated || converse . connection . disconnecting ) {
// TODO: we might need to take prebinding into consideration here.
this . renderLoginPanel ( ) ;
} else if ( ! this . contactspanel || ! this . contactspanel . $el . is ( ':visible' ) ) {
this . renderContactsPanel ( ) ;
}
return this ;
} ,
2014-11-17 19:10:19 +01:00
giveFeedback : function ( message , klass ) {
2014-11-24 22:40:10 +01:00
var $el = this . $ ( '.conn-feedback' ) ;
$el . addClass ( 'conn-feedback' ) . text ( message ) ;
if ( klass ) {
$el . addClass ( klass ) ;
}
2014-11-17 19:10:19 +01:00
} ,
2014-09-22 12:55:14 +02:00
onConnected : function ( ) {
if ( this . model . get ( 'connected' ) ) {
this . render ( ) . initRoster ( ) ;
converse . features . off ( 'add' , this . featureAdded , this ) ;
converse . features . on ( 'add' , this . featureAdded , this ) ;
// Features could have been added before the controlbox was
// initialized. Currently we're only interested in MUC
2015-04-15 22:06:23 +02:00
var feature = converse . features . findWhere ( { 'var' : Strophe . NS . MUC } ) ;
2014-09-22 12:55:14 +02:00
if ( feature ) {
this . featureAdded ( feature ) ;
}
}
2013-06-02 00:21:06 +02:00
} ,
2014-09-15 21:33:44 +02:00
initRoster : function ( ) {
/ * W e i n i t i a l i z e t h e r o s t e r , w h i c h w i l l a p p e a r i n s i d e t h e
* Contacts Panel .
* /
2014-09-15 23:00:52 +02:00
converse . roster = new converse . RosterContacts ( ) ;
converse . roster . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
2014-09-20 15:07:55 +02:00
b64 _sha1 ( 'converse.contacts-' + converse . bare _jid ) ) ;
2014-09-15 23:00:52 +02:00
var rostergroups = new converse . RosterGroups ( ) ;
rostergroups . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
2014-09-20 15:07:55 +02:00
b64 _sha1 ( 'converse.roster.groups' + converse . bare _jid ) ) ;
2014-09-15 23:00:52 +02:00
converse . rosterview = new converse . RosterView ( { model : rostergroups } ) ;
2014-09-15 21:33:44 +02:00
this . contactspanel . $el . append ( converse . rosterview . $el ) ;
2014-09-20 15:07:55 +02:00
converse . rosterview . render ( ) . fetch ( ) . update ( ) ;
2014-09-22 12:55:14 +02:00
return this ;
2014-09-15 21:33:44 +02:00
} ,
2014-09-07 13:18:36 +02:00
renderLoginPanel : function ( ) {
2014-11-24 22:40:10 +01:00
var $feedback = this . $ ( '.conn-feedback' ) ; // we want to still show any existing feedback.
2014-09-07 13:18:36 +02:00
this . $el . html ( converse . templates . controlbox ( this . model . toJSON ( ) ) ) ;
2014-09-18 18:51:23 +02:00
var cfg = { '$parent' : this . $el . find ( '.controlbox-panes' ) , 'model' : this } ;
if ( ! this . loginpanel ) {
this . loginpanel = new converse . LoginPanel ( cfg ) ;
2014-11-17 19:10:19 +01:00
if ( converse . allow _registration ) {
this . registerpanel = new converse . RegisterPanel ( cfg ) ;
}
2014-09-18 18:51:23 +02:00
} else {
this . loginpanel . delegateEvents ( ) . initialize ( cfg ) ;
2014-11-17 19:10:19 +01:00
if ( converse . allow _registration ) {
this . registerpanel . delegateEvents ( ) . initialize ( cfg ) ;
}
2014-09-18 18:51:23 +02:00
}
2014-09-07 13:18:36 +02:00
this . loginpanel . render ( ) ;
2014-11-17 19:10:19 +01:00
if ( converse . allow _registration ) {
this . registerpanel . render ( ) . $el . hide ( ) ;
}
2015-11-05 11:02:23 +01:00
this . initDragResize ( ) . setDimensions ( ) ;
2015-09-25 23:18:30 +02:00
if ( $feedback . length && $feedback . text ( ) !== _ _ ( 'Connecting' ) ) {
2014-11-24 22:40:10 +01:00
this . $ ( '.conn-feedback' ) . replaceWith ( $feedback ) ;
}
2014-11-17 19:10:19 +01:00
return this ;
2014-09-07 13:18:36 +02:00
} ,
renderContactsPanel : function ( ) {
this . $el . html ( converse . templates . controlbox ( this . model . toJSON ( ) ) ) ;
this . contactspanel = new converse . ContactsPanel ( { '$parent' : this . $el . find ( '.controlbox-panes' ) } ) ;
this . contactspanel . render ( ) ;
converse . xmppstatusview = new converse . XMPPStatusView ( { 'model' : converse . xmppstatus } ) ;
converse . xmppstatusview . render ( ) ;
if ( converse . allow _muc ) {
2014-11-15 13:15:02 +01:00
this . roomspanel = new converse . RoomsPanel ( {
'$parent' : this . $el . find ( '.controlbox-panes' ) ,
'model' : new ( Backbone . Model . extend ( {
id : b64 _sha1 ( 'converse.roomspanel' + converse . bare _jid ) , // Required by sessionStorage
browserStorage : new Backbone . BrowserStorage [ converse . storage ] (
b64 _sha1 ( 'converse.roomspanel' + converse . bare _jid ) )
} ) ) ( )
} ) ;
this . roomspanel . render ( ) . model . fetch ( ) ;
if ( ! this . roomspanel . model . get ( 'nick' ) ) {
this . roomspanel . model . save ( { nick : Strophe . getNodeFromJid ( converse . bare _jid ) } ) ;
}
2014-09-07 13:18:36 +02:00
}
2015-11-05 11:02:23 +01:00
this . initDragResize ( ) . setDimensions ( ) ;
2014-09-07 13:18:36 +02:00
} ,
2014-09-22 12:55:14 +02:00
close : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
if ( converse . connection . connected ) {
this . model . save ( { 'closed' : true } ) ;
} else {
this . model . trigger ( 'hide' ) ;
}
converse . emit ( 'controlBoxClosed' , this ) ;
return this ;
} ,
ensureClosedState : function ( ) {
if ( this . model . get ( 'closed' ) ) {
this . hide ( ) ;
} else {
this . show ( ) ;
}
} ,
2014-01-22 22:19:45 +01:00
hide : function ( callback ) {
this . $el . hide ( 'fast' , function ( ) {
2014-03-14 20:52:03 +01:00
converse . refreshWebkit ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxClosed' , this ) ;
2014-01-22 22:19:45 +01:00
converse . controlboxtoggle . show ( function ( ) {
if ( typeof callback === "function" ) {
callback ( ) ;
}
} ) ;
} ) ;
2014-09-22 12:55:14 +02:00
return this ;
2014-01-22 22:19:45 +01:00
} ,
show : function ( ) {
2015-06-27 06:36:25 +02:00
converse . controlboxtoggle . hide ( function ( ) {
2014-02-28 13:22:15 +01:00
this . $el . show ( 'fast' , function ( ) {
2014-09-20 15:07:55 +02:00
if ( converse . rosterview ) {
converse . rosterview . update ( ) ;
}
2014-02-28 13:22:15 +01:00
converse . refreshWebkit ( ) ;
} . bind ( this ) ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'controlBoxOpened' , this ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-01-22 22:19:45 +01:00
return this ;
} ,
2013-06-02 00:21:06 +02:00
featureAdded : function ( feature ) {
2015-10-25 18:49:35 +01:00
if ( ( feature . get ( 'var' ) === Strophe . NS . MUC ) && ( converse . allow _muc ) ) {
2014-11-15 13:15:02 +01:00
this . roomspanel . model . save ( { muc _domain : feature . get ( 'from' ) } ) ;
2013-06-02 00:21:06 +02:00
var $server = this . $el . find ( 'input.new-chatroom-server' ) ;
if ( ! $server . is ( ':focus' ) ) {
2014-11-15 13:15:02 +01:00
$server . val ( this . roomspanel . model . get ( 'muc_domain' ) ) ;
2013-04-20 11:32:54 +02:00
}
2013-03-24 10:18:26 +01:00
}
2013-06-02 00:21:06 +02:00
} ,
switchTab : function ( ev ) {
2014-11-20 11:51:12 +01:00
// TODO: automatically focus the relevant input
2014-11-17 19:10:19 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2013-06-02 00:21:06 +02:00
var $tab = $ ( ev . target ) ,
$sibling = $tab . parent ( ) . siblings ( 'li' ) . children ( 'a' ) ,
2014-05-11 20:08:36 +02:00
$tab _panel = $ ( $tab . attr ( 'href' ) ) ;
$ ( $sibling . attr ( 'href' ) ) . hide ( ) ;
$sibling . removeClass ( 'current' ) ;
$tab . addClass ( 'current' ) ;
$tab _panel . show ( ) ;
2014-11-17 19:10:19 +01:00
return this ;
2013-06-02 00:21:06 +02:00
} ,
2013-08-25 12:06:53 +02:00
showHelpMessages : function ( msgs ) {
// Override showHelpMessages in ChatBoxView, for now do nothing.
2013-06-02 00:21:06 +02:00
return ;
2013-04-20 11:32:54 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2013-03-22 16:43:00 +01:00
2014-09-12 15:23:21 +02:00
this . ChatRoomOccupant = Backbone . Model ;
this . ChatRoomOccupantView = Backbone . View . extend ( {
tagName : 'li' ,
initialize : function ( ) {
2015-03-01 00:55:22 +01:00
this . model . on ( 'add' , this . render , this ) ;
2014-09-12 15:23:21 +02:00
this . model . on ( 'change' , this . render , this ) ;
2014-09-12 19:46:52 +02:00
this . model . on ( 'destroy' , this . destroy , this ) ;
2014-09-12 15:23:21 +02:00
} ,
render : function ( ) {
var $new = converse . templates . occupant (
_ . extend (
this . model . toJSON ( ) , {
'desc_moderator' : _ _ ( 'This user is a moderator' ) ,
2015-10-31 17:30:06 +01:00
'desc_occupant' : _ _ ( 'This user can send messages in this room' ) ,
2014-09-12 15:23:21 +02:00
'desc_visitor' : _ _ ( 'This user can NOT send messages in this room' )
} )
) ;
this . $el . replaceWith ( $new ) ;
this . setElement ( $new , true ) ;
return this ;
2014-09-12 19:46:52 +02:00
} ,
destroy : function ( ) {
this . $el . remove ( ) ;
2014-09-12 15:23:21 +02:00
}
} ) ;
this . ChatRoomOccupants = Backbone . Collection . extend ( {
2015-04-01 15:20:46 +02:00
model : converse . ChatRoomOccupant
2014-09-12 15:23:21 +02:00
} ) ;
this . ChatRoomOccupantsView = Backbone . Overview . extend ( {
tagName : 'div' ,
2015-10-31 17:30:06 +01:00
className : 'occupants' ,
2014-09-12 15:23:21 +02:00
initialize : function ( ) {
this . model . on ( "add" , this . onOccupantAdded , this ) ;
} ,
render : function ( ) {
this . $el . html (
converse . templates . chatroom _sidebar ( {
'label_invitation' : _ _ ( 'Invite...' ) ,
'label_occupants' : _ _ ( 'Occupants' )
} )
) ;
return this . initInviteWidget ( ) ;
} ,
onOccupantAdded : function ( item ) {
var view = this . get ( item . get ( 'id' ) ) ;
if ( ! view ) {
view = this . add ( item . get ( 'id' ) , new converse . ChatRoomOccupantView ( { model : item } ) ) ;
} else {
delete view . model ; // Remove ref to old model to help garbage collection
view . model = item ;
view . initialize ( ) ;
}
2015-10-31 17:30:06 +01:00
this . $ ( '.occupant-list' ) . append ( view . render ( ) . $el ) ;
2014-09-12 15:23:21 +02:00
} ,
2015-03-01 00:55:22 +01:00
parsePresence : function ( pres ) {
var id = Strophe . getResourceFromJid ( pres . getAttribute ( "from" ) ) ;
var data = {
id : id ,
nick : id ,
type : pres . getAttribute ( "type" ) ,
states : [ ]
} ;
_ . each ( pres . childNodes , function ( child ) {
switch ( child . nodeName ) {
case "status" :
data . status = child . textContent || null ;
break ;
case "show" :
data . show = child . textContent || null ;
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" ) ) ;
}
}
} ) ;
}
2014-09-12 15:23:21 +02:00
}
2015-03-01 00:55:22 +01:00
} ) ;
return data ;
} ,
updateOccupantsOnPresence : function ( pres ) {
var occupant ;
var data = this . parsePresence ( pres ) ;
switch ( data . type ) {
case 'error' :
return true ;
case 'unavailable' :
occupant = this . model . get ( data . id ) ;
if ( occupant ) { occupant . destroy ( ) ; }
break ;
default :
occupant = this . model . get ( data . id ) ;
if ( occupant ) {
occupant . save ( data ) ;
} else {
this . model . create ( data ) ;
}
2014-09-12 15:23:21 +02:00
}
} ,
initInviteWidget : function ( ) {
var $el = this . $ ( 'input.invited-contact' ) ;
$el . typeahead ( {
minLength : 1 ,
highlight : true
} , {
name : 'contacts-dataset' ,
source : function ( q , cb ) {
var results = [ ] ;
_ . each ( converse . roster . filter ( contains ( [ 'fullname' , 'jid' ] , q ) ) , function ( n ) {
results . push ( { value : n . get ( 'fullname' ) , jid : n . get ( 'jid' ) } ) ;
} ) ;
cb ( results ) ;
} ,
templates : {
suggestion : _ . template ( '<p data-jid="{{jid}}">{{value}}</p>' )
}
} ) ;
2015-06-27 06:36:25 +02:00
$el . on ( 'typeahead:selected' , function ( ev , suggestion , dname ) {
2014-09-12 15:23:21 +02:00
var reason = prompt (
_ _ ( _ _ _ ( 'You are about to invite %1$s to the chat room "%2$s". ' ) , suggestion . value , this . model . get ( 'id' ) ) +
_ _ ( "You may optionally include a message, explaining the reason for the invitation." )
) ;
if ( reason !== null ) {
2015-03-04 22:27:11 +01:00
this . chatroomview . directInvite ( suggestion . jid , reason ) ;
2014-09-12 15:23:21 +02:00
}
$ ( ev . target ) . typeahead ( 'val' , '' ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-09-12 15:23:21 +02:00
return this ;
2014-10-26 15:52:27 +01:00
}
2014-09-12 15:23:21 +02:00
} ) ;
2013-06-02 00:21:06 +02:00
this . ChatRoomView = converse . ChatBoxView . extend ( {
length : 300 ,
tagName : 'div' ,
2015-10-27 11:17:55 +01:00
className : 'chatbox chatroom' ,
2013-06-02 00:21:06 +02:00
events : {
2014-05-27 18:34:22 +02:00
'click .close-chatbox-button' : 'close' ,
2014-06-02 04:47:23 +02:00
'click .toggle-chatbox-button' : 'minimize' ,
2014-01-22 22:50:39 +01:00
'click .configure-chatroom-button' : 'configureChatRoom' ,
2013-10-20 18:13:34 +02:00
'click .toggle-smiley' : 'toggleEmoticonMenu' ,
2013-11-03 21:28:44 +01:00
'click .toggle-smiley ul li' : 'insertEmoticon' ,
2014-04-24 18:03:30 +02:00
'click .toggle-clear' : 'clearChatRoomMessages' ,
2015-05-26 16:39:06 +02:00
'click .toggle-call' : 'toggleCall' ,
2015-10-31 17:30:06 +01:00
'click .toggle-occupants a' : 'toggleOccupants' ,
2014-03-01 00:51:07 +01:00
'keypress textarea.chat-textarea' : 'keyPressed' ,
2015-09-24 19:41:35 +02:00
'mousedown .dragresize-top' : 'onStartVerticalResize' ,
'mousedown .dragresize-left' : 'onStartHorizontalResize' ,
'mousedown .dragresize-topleft' : 'onStartDiagonalResize'
2013-06-02 00:21:06 +02:00
} ,
2013-10-20 18:13:34 +02:00
is _chatroom : true ,
2013-06-02 00:21:06 +02:00
2014-02-28 03:04:52 +01:00
initialize : function ( ) {
2015-11-05 11:02:23 +01:00
$ ( window ) . on ( 'resize' , _ . debounce ( this . setDimensions . bind ( this ) , 100 ) ) ;
2015-07-19 23:23:02 +02:00
this . model . messages . on ( 'add' , this . onMessageAdded , this ) ;
2014-06-01 20:09:09 +02:00
this . model . on ( 'change:minimized' , function ( item ) {
if ( item . get ( 'minimized' ) ) {
this . hide ( ) ;
} else {
this . maximize ( ) ;
}
} , this ) ;
2015-10-25 18:49:35 +01:00
this . model . on ( 'destroy' , function ( ) {
2015-03-01 01:23:53 +01:00
this . hide ( ) . leave ( ) ;
2015-10-25 18:49:35 +01:00
} , this ) ;
2014-09-12 15:23:21 +02:00
this . occupantsview = new converse . ChatRoomOccupantsView ( {
2014-10-26 15:52:27 +01:00
model : new converse . ChatRoomOccupants ( { nick : this . model . get ( 'nick' ) } )
2014-09-12 15:23:21 +02:00
} ) ;
2015-04-01 15:20:46 +02:00
var id = b64 _sha1 ( 'converse.occupants' + converse . bare _jid + this . model . get ( 'id' ) + this . model . get ( 'nick' ) ) ;
this . occupantsview . model . browserStorage = new Backbone . BrowserStorage [ converse . storage ] ( id ) ;
2014-09-12 19:31:53 +02:00
this . occupantsview . chatroomview = this ;
2015-09-25 23:36:28 +02:00
this . render ( ) . $el . hide ( ) ;
2014-09-12 15:23:21 +02:00
this . occupantsview . model . fetch ( { add : true } ) ;
2015-07-21 11:35:39 +02:00
this . join ( null , { 'maxstanzas' : converse . muc _history _max _stanzas } ) ;
2015-07-21 20:10:16 +02:00
this . fetchMessages ( ) ;
2014-09-20 22:59:29 +02:00
converse . emit ( 'chatRoomOpened' , this ) ;
2014-09-12 15:23:21 +02:00
2014-06-02 04:47:23 +02:00
this . $el . insertAfter ( converse . chatboxviews . get ( "controlbox" ) . $el ) ;
if ( this . model . get ( 'minimized' ) ) {
this . hide ( ) ;
} else {
this . show ( ) ;
}
2014-02-28 03:04:52 +01:00
} ,
render : function ( ) {
this . $el . attr ( 'id' , this . model . get ( 'box_id' ) )
. html ( converse . templates . chatroom ( this . model . toJSON ( ) ) ) ;
2014-09-12 15:23:21 +02:00
this . renderChatArea ( ) ;
2015-07-21 20:10:16 +02:00
this . $content . on ( 'scroll' , _ . debounce ( this . onScroll . bind ( this ) , 100 ) ) ;
2015-09-26 00:11:21 +02:00
this . setWidth ( ) ;
2015-06-22 00:01:31 +02:00
setTimeout ( converse . refreshWebkit , 50 ) ;
2014-09-12 15:23:21 +02:00
return this ;
} ,
renderChatArea : function ( ) {
if ( ! this . $ ( '.chat-area' ) . length ) {
2015-10-24 21:52:54 +02:00
this . $ ( '.chatroom-body' ) . empty ( )
2014-09-12 15:23:21 +02:00
. append (
converse . templates . chatarea ( {
'show_toolbar' : converse . show _toolbar ,
2014-10-26 15:52:27 +01:00
'label_message' : _ _ ( 'Message' )
2014-09-12 15:23:21 +02:00
} ) )
. append ( this . occupantsview . render ( ) . $el ) ;
this . renderToolbar ( ) ;
2015-07-19 12:08:43 +02:00
this . $content = this . $el . find ( '.chat-content' ) ;
2014-09-12 15:23:21 +02:00
}
2015-10-31 17:57:36 +01:00
this . toggleOccupants ( null , true ) ;
2014-02-28 03:04:52 +01:00
return this ;
} ,
2015-11-01 03:31:32 +01:00
2015-10-31 17:57:36 +01:00
toggleOccupants : function ( ev , preserve _state ) {
2014-09-17 22:04:47 +02:00
if ( ev ) {
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
}
2015-10-31 17:57:36 +01:00
if ( preserve _state ) {
// Bit of a hack, to make sure that the sidebar's state doesn't change
this . model . set ( { hidden _occupants : ! this . model . get ( 'hidden_occupants' ) } ) ;
}
2014-09-17 22:04:47 +02:00
var $el = this . $ ( '.icon-hide-users' ) ;
if ( ! this . model . get ( 'hidden_occupants' ) ) {
this . model . save ( { hidden _occupants : true } ) ;
$el . removeClass ( 'icon-hide-users' ) . addClass ( 'icon-show-users' ) ;
2015-11-03 17:42:04 +01:00
this . $ ( '.occupants' ) . addClass ( 'hidden' ) ;
this . $ ( '.chat-area' ) . addClass ( 'full' ) ;
2015-10-24 21:52:54 +02:00
this . scrollDown ( ) ;
2014-09-17 22:04:47 +02:00
} else {
this . model . save ( { hidden _occupants : false } ) ;
$el . removeClass ( 'icon-show-users' ) . addClass ( 'icon-hide-users' ) ;
2015-11-03 17:42:04 +01:00
this . $ ( '.chat-area' ) . removeClass ( 'full' ) ;
2015-10-31 17:30:06 +01:00
this . $ ( 'div.occupants' ) . removeClass ( 'hidden' ) ;
2015-10-24 21:52:54 +02:00
this . scrollDown ( ) ;
2014-09-17 22:04:47 +02:00
}
} ,
2015-03-04 22:27:11 +01:00
directInvite : function ( receiver , reason ) {
var attrs = {
xmlns : 'jabber:x:conference' ,
2015-03-04 23:01:12 +01:00
jid : this . model . get ( 'jid' )
2015-03-04 22:27:11 +01:00
} ;
if ( reason !== null ) { attrs . reason = reason ; }
if ( this . model . get ( 'password' ) ) { attrs . password = this . model . get ( 'password' ) ; }
var invitation = $msg ( {
from : converse . connection . jid ,
to : receiver ,
id : converse . connection . getUniqueId ( )
} ) . c ( 'x' , attrs ) ;
converse . connection . send ( invitation ) ;
converse . emit ( 'roomInviteSent' , this , receiver , reason ) ;
} ,
2014-09-05 19:36:31 +02:00
onCommandError : function ( stanza ) {
this . showStatusNotification ( _ _ ( "Error: could not execute the command" ) , true ) ;
} ,
2015-07-04 10:10:21 +02:00
sendChatRoomMessage : function ( text ) {
2015-03-01 19:22:34 +01:00
var msgid = converse . connection . getUniqueId ( ) ;
var msg = $msg ( {
2015-03-04 23:22:19 +01:00
to : this . model . get ( 'jid' ) ,
2015-03-01 19:22:34 +01:00
from : converse . connection . jid ,
type : 'groupchat' ,
id : msgid
} ) . c ( "body" ) . t ( text ) . up ( )
. c ( "x" , { xmlns : "jabber:x:event" } ) . c ( "composing" ) ;
converse . connection . send ( msg ) ;
2014-10-15 22:28:43 +02:00
var fullname = converse . xmppstatus . get ( 'fullname' ) ;
this . model . messages . create ( {
fullname : _ . isEmpty ( fullname ) ? converse . bare _jid : fullname ,
sender : 'me' ,
time : moment ( ) . format ( ) ,
message : text ,
2015-03-01 19:22:34 +01:00
msgid : msgid
2014-10-15 22:28:43 +02:00
} ) ;
} ,
2015-03-01 20:00:11 +01:00
setAffiliation : function ( room , jid , affiliation , reason , onSuccess , onError ) {
var item = $build ( "item" , { jid : jid , affiliation : affiliation } ) ;
var iq = $iq ( { to : room , type : "set" } ) . c ( "query" , { xmlns : Strophe . NS . MUC _ADMIN } ) . cnode ( item . node ) ;
if ( reason !== null ) { iq . c ( "reason" , reason ) ; }
return converse . connection . sendIQ ( iq . tree ( ) , onSuccess , onError ) ;
} ,
modifyRole : function ( room , nick , role , reason , onSuccess , onError ) {
var item = $build ( "item" , { nick : nick , role : role } ) ;
var iq = $iq ( { to : room , type : "set" } ) . c ( "query" , { xmlns : Strophe . NS . MUC _ADMIN } ) . cnode ( item . node ) ;
if ( reason !== null ) { iq . c ( "reason" , reason ) ; }
return converse . connection . sendIQ ( iq . tree ( ) , onSuccess , onError ) ;
} ,
member : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'member' , reason , handler _cb , error _cb ) ;
} ,
revoke : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'none' , reason , handler _cb , error _cb ) ;
} ,
owner : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'owner' , reason , handler _cb , error _cb ) ;
} ,
admin : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'admin' , reason , handler _cb , error _cb ) ;
} ,
2015-10-12 16:23:05 +02:00
validateRoleChangeCommand : function ( command , args ) {
/ * C h e c k t h a t a c o m m a n d t o c h a n g e a c h a t r o o m u s e r ' s r o l e o r
* affiliation has anough arguments .
* /
// TODO check if first argument is valid
if ( args . length < 1 || args . length > 2 ) {
this . showStatusNotification (
_ _ ( "Error: the \"" + command + "\" command takes two arguments, the user's nickname and optionally a reason." ) ,
true
) ;
return false ;
}
return true ;
} ,
2015-07-04 10:10:21 +02:00
onChatRoomMessageSubmitted : function ( text ) {
/ * G e t s c a l l e d w h e n t h e u s e r p r e s s e s e n t e r t o s e n d o f f a
* message in a chat room .
*
* Parameters :
* ( String ) text - The message text .
* /
2015-10-12 16:23:05 +02:00
var match = text . replace ( /^\s*/ , "" ) . match ( /^\/(.*?)(?: (.*))?$/ ) || [ false , '' , '' ] ,
args = match [ 2 ] && match [ 2 ] . splitOnce ( ' ' ) || [ ] ;
2013-06-02 00:21:06 +02:00
switch ( match [ 1 ] ) {
2015-03-01 20:00:11 +01:00
case 'admin' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'admin' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2015-03-01 20:00:11 +01:00
break ;
2014-09-05 18:27:32 +02:00
case 'ban' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'outcast' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
case 'clear' :
2014-04-24 18:03:30 +02:00
this . clearChatRoomMessages ( ) ;
2013-06-02 00:21:06 +02:00
break ;
2014-09-05 18:27:32 +02:00
case 'deop' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . modifyRole (
2015-10-31 17:30:06 +01:00
this . model . get ( 'jid' ) , args [ 0 ] , 'occupant' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2014-09-05 18:27:32 +02:00
break ;
case 'help' :
2014-10-13 21:15:25 +02:00
this . showHelpMessages ( [
2015-03-01 20:00:11 +01:00
'<strong>/admin</strong>: ' + _ _ ( "Change user's affiliation to admin" ) ,
2014-09-05 18:27:32 +02:00
'<strong>/ban</strong>: ' + _ _ ( 'Ban user from room' ) ,
'<strong>/clear</strong>: ' + _ _ ( 'Remove messages' ) ,
2015-10-31 17:30:06 +01:00
'<strong>/deop</strong>: ' + _ _ ( 'Change user role to occupant' ) ,
2014-09-05 18:27:32 +02:00
'<strong>/help</strong>: ' + _ _ ( 'Show this menu' ) ,
'<strong>/kick</strong>: ' + _ _ ( 'Kick user from room' ) ,
'<strong>/me</strong>: ' + _ _ ( 'Write in 3rd person' ) ,
2015-03-01 20:00:11 +01:00
'<strong>/member</strong>: ' + _ _ ( 'Grant membership to a user' ) ,
2014-09-05 18:27:32 +02:00
'<strong>/mute</strong>: ' + _ _ ( "Remove user's ability to post messages" ) ,
'<strong>/nick</strong>: ' + _ _ ( 'Change your nickname' ) ,
2015-03-01 20:00:11 +01:00
'<strong>/op</strong>: ' + _ _ ( 'Grant moderator role to user' ) ,
'<strong>/owner</strong>: ' + _ _ ( 'Grant ownership of this room' ) ,
'<strong>/revoke</strong>: ' + _ _ ( "Revoke user's membership" ) ,
2014-09-05 18:27:32 +02:00
'<strong>/topic</strong>: ' + _ _ ( 'Set room topic' ) ,
'<strong>/voice</strong>: ' + _ _ ( 'Allow muted user to post messages' )
2014-10-13 21:15:25 +02:00
] ) ;
2013-06-02 00:21:06 +02:00
break ;
case 'kick' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'none' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
2014-09-05 18:27:32 +02:00
case 'mute' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'visitor' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2015-03-01 20:00:11 +01:00
break ;
case 'member' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'member' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2014-09-05 18:27:32 +02:00
break ;
case 'nick' :
2015-03-01 20:00:11 +01:00
converse . connection . send ( $pres ( {
from : converse . connection . jid ,
2015-03-04 23:01:12 +01:00
to : this . getRoomJIDAndNick ( match [ 2 ] ) ,
2015-03-01 20:00:11 +01:00
id : converse . connection . getUniqueId ( )
} ) . tree ( ) ) ;
break ;
case 'owner' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'owner' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
case 'op' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'moderator' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2015-03-01 20:00:11 +01:00
break ;
case 'revoke' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'none' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
2014-09-05 18:27:32 +02:00
case 'topic' :
2015-03-01 20:00:11 +01:00
converse . connection . send (
$msg ( {
to : this . model . get ( 'jid' ) ,
from : converse . connection . jid ,
type : "groupchat"
} ) . c ( "subject" , { xmlns : "jabber:client" } ) . t ( match [ 2 ] ) . tree ( )
) ;
2013-06-02 00:21:06 +02:00
break ;
2014-09-05 18:27:32 +02:00
case 'voice' :
2015-10-12 16:23:05 +02:00
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
2015-03-01 20:00:11 +01:00
this . modifyRole (
2015-10-31 17:30:06 +01:00
this . model . get ( 'jid' ) , args [ 0 ] , 'occupant' , args [ 1 ] ,
2015-06-27 06:36:25 +02:00
undefined , this . onCommandError . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
default :
2015-07-04 10:10:21 +02:00
this . sendChatRoomMessage ( text ) ;
2013-06-02 00:21:06 +02:00
break ;
2013-04-26 14:30:14 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
2015-03-01 00:55:22 +01:00
handleMUCStanza : function ( stanza ) {
var xmlns , xquery , i ;
var from = stanza . getAttribute ( 'from' ) ;
2015-07-21 20:42:14 +02:00
var is _mam = $ ( stanza ) . find ( '[xmlns="' + Strophe . NS . MAM + '"]' ) . length > 0 ;
if ( ! from || ( this . model . get ( 'id' ) !== from . split ( "/" ) [ 0 ] ) || is _mam ) {
2015-03-01 00:55:22 +01:00
return true ;
}
if ( stanza . nodeName === "message" ) {
2015-07-21 20:42:14 +02:00
_ . compose ( this . onChatRoomMessage . bind ( this ) , this . showStatusMessages . bind ( this ) ) ( stanza ) ;
2015-03-01 00:55:22 +01:00
} else if ( stanza . nodeName === "presence" ) {
xquery = stanza . getElementsByTagName ( "x" ) ;
if ( xquery . length > 0 ) {
for ( i = 0 ; i < xquery . length ; i ++ ) {
xmlns = xquery [ i ] . getAttribute ( "xmlns" ) ;
if ( xmlns && xmlns . match ( Strophe . NS . MUC ) ) {
this . onChatRoomPresence ( stanza ) ;
break ;
}
}
}
}
return true ;
} ,
2015-03-04 23:01:12 +01:00
getRoomJIDAndNick : function ( nick ) {
2015-03-01 20:00:11 +01:00
nick = nick || this . model . get ( 'nick' ) ;
2015-03-01 11:22:37 +01:00
var room = this . model . get ( 'jid' ) ;
2015-09-03 16:13:01 +02:00
var node = Strophe . getNodeFromJid ( room ) ;
2015-03-01 00:55:22 +01:00
var domain = Strophe . getDomainFromJid ( room ) ;
2015-03-01 01:23:53 +01:00
return node + "@" + domain + ( nick !== null ? "/" + nick : "" ) ;
} ,
join : function ( password , history _attrs , extended _presence ) {
2015-07-21 11:38:44 +02:00
var stanza = $pres ( {
2015-03-01 00:55:22 +01:00
from : converse . connection . jid ,
2015-03-04 23:01:12 +01:00
to : this . getRoomJIDAndNick ( )
2015-03-01 00:55:22 +01:00
} ) . c ( "x" , {
xmlns : Strophe . NS . MUC
} ) ;
2015-07-21 11:38:44 +02:00
if ( typeof history _attrs === "object" && Object . keys ( history _attrs ) . length ) {
stanza = stanza . c ( "history" , history _attrs ) . up ( ) ;
2015-03-01 00:55:22 +01:00
}
if ( password ) {
2015-07-21 11:38:44 +02:00
stanza . cnode ( Strophe . xmlElement ( "password" , [ ] , password ) ) ;
2013-06-02 00:21:06 +02:00
}
2015-03-01 00:55:22 +01:00
if ( typeof extended _presence !== "undefined" && extended _presence !== null ) {
2015-07-21 11:38:44 +02:00
stanza . up . cnode ( extended _presence ) ;
2015-03-01 00:55:22 +01:00
}
if ( ! this . handler ) {
2015-06-27 06:36:25 +02:00
this . handler = converse . connection . addHandler ( this . handleMUCStanza . bind ( this ) ) ;
2015-03-01 00:55:22 +01:00
}
2015-03-04 23:01:12 +01:00
this . model . set ( 'connection_status' , Strophe . Status . CONNECTING ) ;
2015-07-21 11:38:44 +02:00
return converse . connection . send ( stanza ) ;
2013-06-02 00:21:06 +02:00
} ,
2015-03-01 01:23:53 +01:00
leave : function ( exit _msg ) {
2015-03-01 11:58:07 +01:00
var presenceid = converse . connection . getUniqueId ( ) ;
2015-03-01 01:23:53 +01:00
var presence = $pres ( {
type : "unavailable" ,
2015-03-01 11:58:07 +01:00
id : presenceid ,
2015-03-01 01:23:53 +01:00
from : converse . connection . jid ,
2015-03-04 23:01:12 +01:00
to : this . getRoomJIDAndNick ( )
2015-03-01 01:23:53 +01:00
} ) ;
if ( exit _msg !== null ) {
presence . c ( "status" , exit _msg ) ;
}
converse . connection . addHandler (
2015-06-27 06:36:25 +02:00
function ( ) { this . model . set ( 'connection_status' , Strophe . Status . DISCONNECTED ) ; } . bind ( this ) ,
2015-03-01 01:23:53 +01:00
null , "presence" , null , presenceid ) ;
converse . connection . send ( presence ) ;
2013-06-02 00:21:06 +02:00
} ,
renderConfigurationForm : function ( stanza ) {
2015-10-31 14:02:14 +01:00
var $form = this . $el . find ( 'form.chatroom-form' ) ,
2015-10-31 17:18:31 +01:00
$fieldset = $form . children ( 'fieldset:first' ) ,
2013-06-02 00:21:06 +02:00
$stanza = $ ( stanza ) ,
$fields = $stanza . find ( 'field' ) ,
2014-12-07 11:13:50 +01:00
title = $stanza . find ( 'title' ) . text ( ) ,
instructions = $stanza . find ( 'instructions' ) . text ( ) ;
2015-10-31 17:18:31 +01:00
$fieldset . find ( 'span.spinner' ) . remove ( ) ;
$fieldset . append ( $ ( '<legend>' ) . text ( title ) ) ;
2015-10-25 18:49:35 +01:00
if ( instructions && instructions !== title ) {
2015-10-31 17:18:31 +01:00
$fieldset . append ( $ ( '<p class="instructions">' ) . text ( instructions ) ) ;
2013-06-02 00:21:06 +02:00
}
2014-11-16 12:47:30 +01:00
_ . each ( $fields , function ( field ) {
2015-10-31 17:18:31 +01:00
$fieldset . append ( utils . xForm2webForm ( $ ( field ) , $stanza ) ) ;
2014-11-16 12:47:30 +01:00
} ) ;
2015-10-31 17:18:31 +01:00
$form . append ( '<fieldset></fieldset>' ) ;
$fieldset = $form . children ( 'fieldset:last' ) ;
$fieldset . append ( '<input type="submit" class="pure-button button-primary" value="' + _ _ ( 'Save' ) + '"/>' ) ;
$fieldset . append ( '<input type="button" class="pure-button button-cancel" value="' + _ _ ( 'Cancel' ) + '"/>' ) ;
$fieldset . find ( 'input[type=button]' ) . on ( 'click' , this . cancelConfiguration . bind ( this ) ) ;
2015-03-04 22:27:11 +01:00
$form . on ( 'submit' , this . saveConfiguration . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
} ,
2015-03-01 23:57:49 +01:00
sendConfiguration : function ( config , onSuccess , onError ) {
// Send an IQ stanza with the room configuration.
var iq = $iq ( { to : this . model . get ( 'jid' ) , type : "set" } )
. c ( "query" , { xmlns : Strophe . NS . MUC _OWNER } )
2015-07-17 16:42:00 +02:00
. c ( "x" , { xmlns : Strophe . NS . XFORM , type : "submit" } ) ;
2015-03-04 22:27:11 +01:00
_ . each ( config , function ( node ) { iq . cnode ( node ) . up ( ) ; } ) ;
2015-03-01 23:57:49 +01:00
return converse . connection . sendIQ ( iq . tree ( ) , onSuccess , onError ) ;
} ,
2013-06-02 00:21:06 +02:00
saveConfiguration : function ( ev ) {
ev . preventDefault ( ) ;
var that = this ;
var $inputs = $ ( ev . target ) . find ( ':input:not([type=button]):not([type=submit])' ) ,
count = $inputs . length ,
configArray = [ ] ;
$inputs . each ( function ( ) {
2014-11-17 09:44:42 +01:00
configArray . push ( utils . webForm2xForm ( this ) ) ;
2013-06-02 00:21:06 +02:00
if ( ! -- count ) {
2015-03-01 23:57:49 +01:00
that . sendConfiguration (
2013-06-02 00:21:06 +02:00
configArray ,
2015-06-27 06:36:25 +02:00
that . onConfigSaved . bind ( that ) ,
that . onErrorConfigSaved . bind ( that )
2013-06-02 00:21:06 +02:00
) ;
2013-05-31 16:55:58 +02:00
}
} ) ;
2013-06-02 00:21:06 +02:00
this . $el . find ( 'div.chatroom-form-container' ) . hide (
function ( ) {
$ ( this ) . remove ( ) ;
2015-10-31 17:57:36 +01:00
that . $el . find ( '.chat-area' ) . removeClass ( 'hidden' ) ;
2015-10-31 17:30:06 +01:00
that . $el . find ( '.occupants' ) . removeClass ( 'hidden' ) ;
2013-06-02 00:21:06 +02:00
} ) ;
} ,
2013-05-13 09:10:47 +02:00
2013-06-02 00:21:06 +02:00
onConfigSaved : function ( stanza ) {
2014-11-17 09:44:42 +01:00
// TODO: provide feedback
2013-06-02 00:21:06 +02:00
} ,
onErrorConfigSaved : function ( stanza ) {
2013-08-25 12:06:53 +02:00
this . showStatusNotification ( _ _ ( "An error occurred while trying to save the form." ) ) ;
2013-06-02 00:21:06 +02:00
} ,
cancelConfiguration : function ( ev ) {
ev . preventDefault ( ) ;
var that = this ;
this . $el . find ( 'div.chatroom-form-container' ) . hide (
function ( ) {
$ ( this ) . remove ( ) ;
2015-10-31 17:57:36 +01:00
that . $el . find ( '.chat-area' ) . removeClass ( 'hidden' ) ;
2015-10-31 17:30:06 +01:00
that . $el . find ( '.occupants' ) . removeClass ( 'hidden' ) ;
2013-06-02 00:21:06 +02:00
} ) ;
} ,
configureChatRoom : function ( ev ) {
ev . preventDefault ( ) ;
if ( this . $el . find ( 'div.chatroom-form-container' ) . length ) {
return ;
}
2015-10-31 17:57:36 +01:00
this . $ ( '.chatroom-body' ) . children ( ) . addClass ( 'hidden' ) ;
2015-10-31 14:02:14 +01:00
this . $ ( '.chatroom-body' ) . append ( converse . templates . chatroom _form ( ) ) ;
2015-03-04 22:27:11 +01:00
converse . connection . sendIQ (
$iq ( {
to : this . model . get ( 'jid' ) ,
type : "get"
} ) . c ( "query" , { xmlns : Strophe . NS . MUC _OWNER } ) . tree ( ) ,
this . renderConfigurationForm . bind ( this )
2013-06-02 00:21:06 +02:00
) ;
} ,
submitPassword : function ( ev ) {
ev . preventDefault ( ) ;
var password = this . $el . find ( '.chatroom-form' ) . find ( 'input[type=password]' ) . val ( ) ;
2014-09-20 22:59:29 +02:00
this . $el . find ( '.chatroom-form-container' ) . replaceWith ( '<span class="spinner centered"/>' ) ;
2015-03-01 00:55:22 +01:00
this . join ( password ) ;
2013-06-02 00:21:06 +02:00
} ,
renderPasswordForm : function ( ) {
2015-10-24 21:52:54 +02:00
this . $ ( '.chatroom-body' ) . children ( ) . hide ( ) ;
2014-09-20 22:59:29 +02:00
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
2015-10-24 21:52:54 +02:00
this . $ ( '.chatroom-body' ) . append (
2014-09-20 22:59:29 +02:00
converse . templates . chatroom _password _form ( {
heading : _ _ ( 'This chatroom requires a password' ) ,
label _password : _ _ ( 'Password: ' ) ,
label _submit : _ _ ( 'Submit' )
} ) ) ;
2015-06-27 06:36:25 +02:00
this . $ ( '.chatroom-form' ) . on ( 'submit' , this . submitPassword . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
} ,
showDisconnectMessage : function ( msg ) {
2014-09-20 22:59:29 +02:00
this . $ ( '.chat-area' ) . hide ( ) ;
2015-10-31 17:30:06 +01:00
this . $ ( '.occupants' ) . hide ( ) ;
2014-09-20 22:59:29 +02:00
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
2015-10-24 21:52:54 +02:00
this . $ ( '.chatroom-body' ) . append ( $ ( '<p>' + msg + '</p>' ) ) ;
2013-06-02 00:21:06 +02:00
} ,
2014-09-06 23:34:39 +02:00
/* http:/ / xmpp . org / extensions / xep - 0045. html
2014-09-06 12:25:37 +02:00
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* 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
* /
2013-06-02 00:21:06 +02:00
infoMessages : {
100 : _ _ ( 'This room is not anonymous' ) ,
102 : _ _ ( 'This room now shows unavailable members' ) ,
103 : _ _ ( 'This room does not show unavailable members' ) ,
104 : _ _ ( 'Non-privacy-related room configuration has changed' ) ,
170 : _ _ ( 'Room logging is now enabled' ) ,
171 : _ _ ( 'Room logging is now disabled' ) ,
172 : _ _ ( 'This room is now non-anonymous' ) ,
173 : _ _ ( 'This room is now semi-anonymous' ) ,
174 : _ _ ( 'This room is now fully-anonymous' ) ,
2014-10-26 15:52:27 +01:00
201 : _ _ ( 'A new room has been created' )
2014-09-06 12:25:37 +02:00
} ,
disconnectMessages : {
301 : _ _ ( 'You have been banned from this room' ) ,
307 : _ _ ( 'You have been kicked from this room' ) ,
321 : _ _ ( "You have been removed from this room because of an affiliation change" ) ,
322 : _ _ ( "You have been removed from this room because the room has changed to members-only and you're not a member" ) ,
332 : _ _ ( "You have been removed from this room because the MUC (Multi-user chat) service is being shut down." )
2013-06-02 00:21:06 +02:00
} ,
actionInfoMessages : {
2013-08-26 16:21:32 +02:00
/ * X X X : N o t e t h e t r i p l e u n d e r s c o r e f u n c t i o n a n d n o t d o u b l e
* underscore .
*
* This is a hack . We can ' t pass the strings to _ _ because we
* don ' t yet know what the variable to interpolate is .
*
* Triple underscore will just return the string again , but we
* can then at least tell gettext to scan for it so that these
* strings are picked up by the translation machinery .
* /
301 : _ _ _ ( "<strong>%1$s</strong> has been banned" ) ,
2014-09-06 12:25:37 +02:00
303 : _ _ _ ( "<strong>%1$s</strong>'s nickname has changed" ) ,
2013-08-26 16:21:32 +02:00
307 : _ _ _ ( "<strong>%1$s</strong> has been kicked out" ) ,
321 : _ _ _ ( "<strong>%1$s</strong> has been removed because of an affiliation change" ) ,
322 : _ _ _ ( "<strong>%1$s</strong> has been removed for not being a member" )
2013-06-02 00:21:06 +02:00
} ,
2014-09-06 12:25:37 +02:00
newNicknameMessages : {
210 : _ _ _ ( 'Your nickname has been automatically changed to: <strong>%1$s</strong>' ) ,
303 : _ _ _ ( 'Your nickname has been changed to: <strong>%1$s</strong>' )
2013-06-02 00:21:06 +02:00
} ,
2015-07-21 20:42:14 +02:00
showStatusMessages : function ( el , is _self ) {
2014-09-06 12:25:37 +02:00
/ * C h e c k f o r s t a t u s c o d e s a n d c o m m u n i c a t e t h e i r p u r p o s e t o t h e u s e r .
* Allow user to configure chat room if they are the owner .
* See : http : //xmpp.org/registrar/mucstatus.html
* /
2015-07-21 20:42:14 +02:00
var $el = $ ( el ) ,
disconnect _msgs = [ ] ,
2014-09-06 12:25:37 +02:00
msgs = [ ] ,
reasons = [ ] ;
2015-06-27 06:36:25 +02:00
$el . find ( 'x[xmlns="' + Strophe . NS . MUC _USER + '"]' ) . each ( function ( idx , x ) {
2014-09-06 12:25:37 +02:00
var $item = $ ( x ) . find ( 'item' ) ;
if ( Strophe . getBareJidFromJid ( $item . attr ( 'jid' ) ) === converse . bare _jid && $item . attr ( 'affiliation' ) === 'owner' ) {
this . $el . find ( 'a.configure-chatroom-button' ) . show ( ) ;
2013-05-12 13:53:37 +02:00
}
2014-09-06 12:25:37 +02:00
$ ( x ) . find ( 'item reason' ) . each ( function ( idx , reason ) {
if ( $ ( reason ) . text ( ) ) {
reasons . push ( $ ( reason ) . text ( ) ) ;
}
} ) ;
2015-06-27 06:36:25 +02:00
$ ( x ) . find ( 'status' ) . each ( function ( idx , stat ) {
2014-09-06 12:25:37 +02:00
var code = stat . getAttribute ( 'code' ) ;
2015-03-01 11:22:37 +01:00
var from _nick = Strophe . unescapeNode ( Strophe . getResourceFromJid ( $el . attr ( 'from' ) ) ) ;
if ( is _self && code === "210" ) {
msgs . push ( _ _ ( this . newNicknameMessages [ code ] , from _nick ) ) ;
} else if ( is _self && code === "303" ) {
2014-09-06 12:25:37 +02:00
msgs . push ( _ _ ( this . newNicknameMessages [ code ] , $item . attr ( 'nick' ) ) ) ;
} else if ( is _self && _ . contains ( _ . keys ( this . disconnectMessages ) , code ) ) {
disconnect _msgs . push ( this . disconnectMessages [ code ] ) ;
} else if ( ! is _self && _ . contains ( _ . keys ( this . actionInfoMessages ) , code ) ) {
2015-03-01 11:22:37 +01:00
msgs . push ( _ _ ( this . actionInfoMessages [ code ] , from _nick ) ) ;
2014-09-06 12:25:37 +02:00
} else if ( _ . contains ( _ . keys ( this . infoMessages ) , code ) ) {
msgs . push ( this . infoMessages [ code ] ) ;
} else if ( code !== '110' ) {
if ( $ ( stat ) . text ( ) ) {
msgs . push ( $ ( stat ) . text ( ) ) ; // Sometimes the status contains human readable text and not a code.
}
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
} . bind ( this ) ) ;
2014-09-06 12:25:37 +02:00
2013-06-02 00:21:06 +02:00
if ( disconnect _msgs . length > 0 ) {
for ( i = 0 ; i < disconnect _msgs . length ; i ++ ) {
this . showDisconnectMessage ( disconnect _msgs [ i ] ) ;
}
2014-09-06 12:25:37 +02:00
for ( i = 0 ; i < reasons . length ; i ++ ) {
this . showDisconnectMessage ( _ _ ( 'The reason given is: "' + reasons [ i ] + '"' ) , true ) ;
}
2015-03-04 23:01:12 +01:00
this . model . set ( 'connection_status' , Strophe . Status . DISCONNECTED ) ;
2013-06-02 00:21:06 +02:00
return ;
}
2014-09-06 12:25:37 +02:00
for ( i = 0 ; i < msgs . length ; i ++ ) {
2015-07-19 12:08:43 +02:00
this . $content . append ( converse . templates . info ( { message : msgs [ i ] } ) ) ;
2013-06-02 00:21:06 +02:00
}
2014-09-06 12:25:37 +02:00
for ( i = 0 ; i < reasons . length ; i ++ ) {
this . showStatusNotification ( _ _ ( 'The reason given is: "' + reasons [ i ] + '"' ) , true ) ;
2013-06-02 00:21:06 +02:00
}
2015-07-21 20:42:14 +02:00
this . scrollDown ( ) ;
return el ;
2013-06-02 00:21:06 +02:00
} ,
2015-03-01 11:58:07 +01:00
showErrorMessage : function ( $error ) {
2013-06-02 00:21:06 +02:00
// We didn't enter the room, so we must remove it from the MUC
// add-on
2015-10-25 18:49:35 +01:00
if ( $error . attr ( 'type' ) === 'auth' ) {
2013-06-02 00:21:06 +02:00
if ( $error . find ( 'not-authorized' ) . length ) {
this . renderPasswordForm ( ) ;
} else if ( $error . find ( 'registration-required' ) . length ) {
this . showDisconnectMessage ( _ _ ( 'You are not on the member list of this room' ) ) ;
} else if ( $error . find ( 'forbidden' ) . length ) {
this . showDisconnectMessage ( _ _ ( 'You have been banned from this room' ) ) ;
}
2015-10-25 18:49:35 +01:00
} else if ( $error . attr ( 'type' ) === 'modify' ) {
2013-06-02 00:21:06 +02:00
if ( $error . find ( 'jid-malformed' ) . length ) {
this . showDisconnectMessage ( _ _ ( 'No nickname was specified' ) ) ;
}
2015-10-25 18:49:35 +01:00
} else if ( $error . attr ( 'type' ) === 'cancel' ) {
2013-06-02 00:21:06 +02:00
if ( $error . find ( 'not-allowed' ) . length ) {
this . showDisconnectMessage ( _ _ ( 'You are not allowed to create new rooms' ) ) ;
} else if ( $error . find ( 'not-acceptable' ) . length ) {
this . showDisconnectMessage ( _ _ ( "Your nickname doesn't conform to this room's policies" ) ) ;
} else if ( $error . find ( 'conflict' ) . length ) {
2014-08-30 16:00:52 +02:00
// TODO: give user the option of choosing a different
// nickname
2013-06-02 00:21:06 +02:00
this . showDisconnectMessage ( _ _ ( "Your nickname is already taken" ) ) ;
} else if ( $error . find ( 'item-not-found' ) . length ) {
this . showDisconnectMessage ( _ _ ( "This room does not (yet) exist" ) ) ;
} else if ( $error . find ( 'service-unavailable' ) . length ) {
this . showDisconnectMessage ( _ _ ( "This room has reached it's maximum number of occupants" ) ) ;
}
}
} ,
2015-03-01 00:55:22 +01:00
onChatRoomPresence : function ( pres ) {
var $presence = $ ( pres ) , is _self ;
var nick = this . model . get ( 'nick' ) ;
2013-06-02 00:21:06 +02:00
if ( $presence . attr ( 'type' ) === 'error' ) {
2015-03-04 23:01:12 +01:00
this . model . set ( 'connection_status' , Strophe . Status . DISCONNECTED ) ;
2015-03-01 11:58:07 +01:00
this . showErrorMessage ( $presence . find ( 'error' ) ) ;
2013-06-02 00:21:06 +02:00
} else {
2015-03-01 00:55:22 +01:00
is _self = ( $presence . find ( "status[code='110']" ) . length ) ||
2015-10-25 18:49:35 +01:00
( $presence . attr ( 'from' ) === this . model . get ( 'id' ) + '/' + Strophe . escapeNode ( nick ) ) ;
2015-03-04 23:01:12 +01:00
if ( this . model . get ( 'connection_status' ) !== Strophe . Status . CONNECTED ) {
this . model . set ( 'connection_status' , Strophe . Status . CONNECTED ) ;
2014-09-20 22:59:29 +02:00
}
2015-07-21 20:42:14 +02:00
this . showStatusMessages ( pres , is _self ) ;
2013-06-02 00:21:06 +02:00
}
2015-03-01 00:55:22 +01:00
this . occupantsview . updateOccupantsOnPresence ( pres ) ;
2013-06-02 00:21:06 +02:00
} ,
onChatRoomMessage : function ( message ) {
var $message = $ ( message ) ,
2015-07-21 20:10:16 +02:00
archive _id = $message . find ( 'result[xmlns="' + Strophe . NS . MAM + '"]' ) . attr ( 'id' ) ,
delayed = $message . find ( 'delay' ) . length > 0 ,
$forwarded = $message . find ( 'forwarded' ) ,
$delay ;
if ( $forwarded . length ) {
$message = $forwarded . children ( 'message' ) ;
$delay = $forwarded . children ( 'delay' ) ;
delayed = $delay . length > 0 ;
}
var body = $message . children ( 'body' ) . text ( ) ,
2013-06-02 00:21:06 +02:00
jid = $message . attr ( 'from' ) ,
2014-10-13 21:15:25 +02:00
msgid = $message . attr ( 'id' ) ,
2013-06-02 00:21:06 +02:00
resource = Strophe . getResourceFromJid ( jid ) ,
sender = resource && Strophe . unescapeNode ( resource ) || '' ,
2014-09-12 22:23:42 +02:00
subject = $message . children ( 'subject' ) . text ( ) ;
2014-10-13 21:15:25 +02:00
2014-11-08 15:50:00 +01:00
if ( msgid && this . model . messages . findWhere ( { msgid : msgid } ) ) {
2014-10-13 21:15:25 +02:00
return true ; // We already have this message stored.
}
2013-06-02 00:21:06 +02:00
if ( subject ) {
this . $el . find ( '.chatroom-topic' ) . text ( subject ) . attr ( 'title' , subject ) ;
// # For translators: the %1$s and %2$s parts will get replaced by the user and topic text respectively
// # Example: Topic set by JC Brand to: Hello World!
2015-07-19 12:08:43 +02:00
this . $content . append (
2013-12-30 20:27:57 +01:00
converse . templates . info ( {
2013-08-25 12:06:53 +02:00
'message' : _ _ ( 'Topic set by %1$s to: %2$s' , sender , subject )
} ) ) ;
2013-06-02 00:21:06 +02:00
}
2014-09-12 22:23:42 +02:00
if ( sender === '' ) {
return true ;
}
2015-07-21 20:10:16 +02:00
this . model . createMessage ( $message , $delay , archive _id ) ;
2014-09-12 22:23:42 +02:00
if ( ! delayed && sender !== this . model . get ( 'nick' ) && ( new RegExp ( "\\b" + this . model . get ( 'nick' ) + "\\b" ) ) . test ( body ) ) {
2015-03-16 17:27:26 +01:00
converse . playNotification ( ) ;
2014-09-03 18:47:24 +02:00
}
2014-09-12 22:23:42 +02:00
if ( sender !== this . model . get ( 'nick' ) ) {
2013-12-16 18:19:25 +01:00
// We only emit an event if it's not our own message
2014-07-06 18:31:17 +02:00
converse . emit ( 'message' , message ) ;
2013-12-16 18:19:25 +01:00
}
2013-06-02 00:21:06 +02:00
return true ;
2013-05-11 16:19:42 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2013-05-12 13:53:37 +02:00
2013-06-02 00:21:06 +02:00
this . ChatBoxes = Backbone . Collection . extend ( {
model : converse . ChatBox ,
2014-05-27 18:34:22 +02:00
comparator : 'time_opened' ,
2013-05-12 13:53:37 +02:00
2014-02-11 12:44:27 +01:00
registerMessageHandler : function ( ) {
converse . connection . addHandler (
2015-06-27 06:36:25 +02:00
function ( message ) {
2014-03-05 00:23:45 +01:00
this . onMessage ( message ) ;
2014-02-11 12:44:27 +01:00
return true ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) , null , 'message' , 'chat' ) ;
2014-08-30 16:00:52 +02:00
converse . connection . addHandler (
2015-06-27 06:36:25 +02:00
function ( message ) {
2014-08-30 16:00:52 +02:00
this . onInvite ( message ) ;
return true ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) , 'jabber:x:conference' , 'message' ) ;
2014-02-11 12:44:27 +01:00
} ,
2013-06-02 00:21:06 +02:00
onConnected : function ( ) {
2014-06-30 18:53:58 +02:00
this . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
2014-04-19 05:12:24 +02:00
b64 _sha1 ( 'converse.chatboxes-' + converse . bare _jid ) ) ;
2014-02-11 12:44:27 +01:00
this . registerMessageHandler ( ) ;
2013-06-02 00:21:06 +02:00
this . fetch ( {
add : true ,
2015-06-27 06:36:25 +02:00
success : function ( collection , resp ) {
2015-05-31 14:46:12 +02:00
collection . each ( function ( chatbox ) {
if ( chatbox . get ( 'id' ) !== 'controlbox' && ! chatbox . get ( 'minimized' ) ) {
chatbox . trigger ( 'show' ) ;
}
} ) ;
2014-09-22 12:55:14 +02:00
if ( ! _ . include ( _ . pluck ( resp , 'id' ) , 'controlbox' ) ) {
this . add ( {
id : 'controlbox' ,
box _id : 'controlbox'
} ) ;
2013-06-02 00:21:06 +02:00
}
2014-09-22 12:55:14 +02:00
this . get ( 'controlbox' ) . save ( { connected : true } ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this )
2013-05-12 13:53:37 +02:00
} ) ;
2013-06-02 00:21:06 +02:00
} ,
2013-05-12 13:53:37 +02:00
2014-08-20 21:00:28 +02:00
isOnlyChatStateNotification : function ( $msg ) {
// See XEP-0085 Chat State Notification
return (
$msg . find ( 'body' ) . length === 0 && (
$msg . find ( ACTIVE ) . length !== 0 ||
$msg . find ( COMPOSING ) . length !== 0 ||
$msg . find ( INACTIVE ) . length !== 0 ||
2014-09-22 16:35:36 +02:00
$msg . find ( PAUSED ) . length !== 0 ||
$msg . find ( GONE ) . length !== 0
2014-08-20 21:00:28 +02:00
)
) ;
} ,
2014-08-30 16:00:52 +02:00
onInvite : function ( message ) {
var $message = $ ( message ) ,
$x = $message . children ( 'x[xmlns="jabber:x:conference"]' ) ,
from = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ,
room _jid = $x . attr ( 'jid' ) ,
2014-08-30 17:56:45 +02:00
reason = $x . attr ( 'reason' ) ,
2014-08-30 16:00:52 +02:00
contact = converse . roster . get ( from ) ,
2014-08-30 17:56:45 +02:00
result ;
2014-08-31 01:17:52 +02:00
if ( ! reason ) {
2014-08-30 16:00:52 +02:00
result = confirm (
2014-08-31 01:17:52 +02:00
_ _ ( _ _ _ ( "%1$s has invited you to join a chat room: %2$s" ) , contact . get ( 'fullname' ) , room _jid )
2014-08-30 16:00:52 +02:00
) ;
2014-08-30 17:56:45 +02:00
} else {
result = confirm (
2014-08-31 01:17:52 +02:00
_ _ ( _ _ _ ( '%1$s has invited you to join a chat room: %2$s, and left the following reason: "%3$s"' ) ,
contact . get ( 'fullname' ) , room _jid , reason )
2014-08-30 17:56:45 +02:00
) ;
}
2014-08-30 16:00:52 +02:00
if ( result === true ) {
var chatroom = converse . chatboxviews . showChat ( {
'id' : room _jid ,
'jid' : room _jid ,
'name' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( room _jid ) ) ,
'nick' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( converse . connection . jid ) ) ,
'chatroom' : true ,
2014-08-31 01:17:52 +02:00
'box_id' : b64 _sha1 ( room _jid ) ,
'password' : $x . attr ( 'password' )
2014-08-30 16:00:52 +02:00
} ) ;
2015-03-04 23:01:12 +01:00
if ( ! _ . contains (
[ Strophe . Status . CONNECTING , Strophe . Status . CONNECTED ] ,
chatroom . get ( 'connection_status' ) )
) {
2015-03-01 00:55:22 +01:00
converse . chatboxviews . get ( room _jid ) . join ( null ) ;
2014-08-30 16:00:52 +02:00
}
}
} ,
2014-03-05 00:23:45 +01:00
onMessage : function ( message ) {
2015-04-02 02:01:53 +02:00
/ * H a n d l e r m e t h o d f o r a l l i n c o m i n g s i n g l e - u s e r c h a t " m e s s a g e " s t a n z a s .
* /
2015-07-01 19:43:58 +02:00
var $message = $ ( message ) ,
2015-07-20 19:42:36 +02:00
contact _jid , $forwarded , $delay , from _bare _jid , from _resource , is _me , msgid ,
2015-10-25 18:49:35 +01:00
chatbox , resource ,
2015-07-02 11:23:52 +02:00
from _jid = $message . attr ( 'from' ) ,
2015-07-01 19:43:58 +02:00
to _jid = $message . attr ( 'to' ) ,
2015-07-17 23:13:43 +02:00
to _resource = Strophe . getResourceFromJid ( to _jid ) ,
archive _id = $message . find ( 'result[xmlns="' + Strophe . NS . MAM + '"]' ) . attr ( 'id' ) ;
2015-04-16 09:37:25 +02:00
2015-07-10 10:35:33 +02:00
if ( to _resource && to _resource !== converse . resource ) {
2015-07-02 11:23:52 +02:00
converse . log ( 'Ignore incoming message intended for a different resource: ' + to _jid , 'info' ) ;
2015-04-16 09:37:25 +02:00
return true ;
}
2015-07-02 11:23:52 +02:00
if ( from _jid === converse . connection . jid ) {
2015-07-02 10:41:11 +02:00
// FIXME: Forwarded messages should be sent to specific resources, not broadcasted
converse . log ( "Ignore incoming message sent from this client's JID: " + from _jid , 'info' ) ;
2013-06-02 00:21:06 +02:00
return true ;
}
2015-07-17 16:49:58 +02:00
$forwarded = $message . find ( 'forwarded' ) ;
2013-06-02 00:21:06 +02:00
if ( $forwarded . length ) {
$message = $forwarded . children ( 'message' ) ;
2015-07-17 16:49:58 +02:00
$delay = $forwarded . children ( 'delay' ) ;
2015-07-02 11:23:52 +02:00
from _jid = $message . attr ( 'from' ) ;
2015-07-03 11:55:13 +02:00
to _jid = $message . attr ( 'to' ) ;
2013-06-02 00:21:06 +02:00
}
2015-07-02 11:23:52 +02:00
from _bare _jid = Strophe . getBareJidFromJid ( from _jid ) ;
from _resource = Strophe . getResourceFromJid ( from _jid ) ;
2015-10-25 18:49:35 +01:00
is _me = from _bare _jid === converse . bare _jid ;
2015-07-20 19:42:36 +02:00
msgid = $message . attr ( 'id' ) ;
2014-11-15 15:38:15 +01:00
2015-07-01 19:43:58 +02:00
if ( is _me ) {
2013-06-02 00:21:06 +02:00
// I am the sender, so this must be a forwarded message...
2015-07-01 19:43:58 +02:00
contact _jid = Strophe . getBareJidFromJid ( to _jid ) ;
resource = Strophe . getResourceFromJid ( to _jid ) ;
2013-06-02 00:21:06 +02:00
} else {
2015-07-01 19:43:58 +02:00
contact _jid = from _bare _jid ;
resource = from _resource ;
2013-06-02 00:21:06 +02:00
}
2015-07-17 16:49:58 +02:00
// Get chat box, but only create a new one when the message has a body.
chatbox = this . getChatBox ( contact _jid , $message . find ( 'body' ) . length > 0 ) ;
2015-07-19 23:54:45 +02:00
if ( ! chatbox ) {
return true ;
}
2014-11-08 16:58:42 +01:00
if ( msgid && chatbox . messages . findWhere ( { msgid : msgid } ) ) {
return true ; // We already have this message stored.
}
2015-07-19 23:54:45 +02:00
if ( ! this . isOnlyChatStateNotification ( $message ) && ! is _me && ! $forwarded . length ) {
2015-03-16 17:27:26 +01:00
converse . playNotification ( ) ;
2014-08-20 21:00:28 +02:00
}
2015-07-17 23:13:43 +02:00
chatbox . receiveMessage ( $message , $delay , archive _id ) ;
2014-11-09 18:07:06 +01:00
converse . roster . addResource ( contact _jid , resource ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'message' , message ) ;
2013-06-02 00:21:06 +02:00
return true ;
2015-07-17 16:49:58 +02:00
} ,
getChatBox : function ( jid , create ) {
/ * R e t u r n s a c h a t b o x o r o p t i o n a l l y r e t u r n a n e w l y
* created one if one doesn ' t exist .
*
* Parameters :
* ( String ) jid - The JID of the user whose chat box we want
* ( Boolean ) create - Should a new chat box be created if none exists ?
* /
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
var chatbox = this . get ( bare _jid ) ;
if ( ! chatbox && create ) {
var roster _item = converse . roster . get ( bare _jid ) ;
if ( roster _item === undefined ) {
converse . log ( 'Could not get roster item for JID ' + bare _jid , 'error' ) ;
return ;
}
chatbox = this . create ( {
'id' : bare _jid ,
'jid' : bare _jid ,
'fullname' : _ . isEmpty ( roster _item . get ( 'fullname' ) ) ? jid : roster _item . get ( 'fullname' ) ,
'image_type' : roster _item . get ( 'image_type' ) ,
'image' : roster _item . get ( 'image' ) ,
'url' : roster _item . get ( 'url' )
} ) ;
}
return chatbox ;
2013-06-02 00:21:06 +02:00
}
} ) ;
2013-05-12 13:53:37 +02:00
2014-06-01 15:28:52 +02:00
this . ChatBoxViews = Backbone . Overview . extend ( {
2013-06-02 00:21:06 +02:00
initialize : function ( ) {
2014-09-12 15:23:21 +02:00
this . model . on ( "add" , this . onChatBoxAdded , this ) ;
2014-06-11 22:53:14 +02:00
this . model . on ( "change:minimized" , function ( item ) {
2015-05-31 14:50:11 +02:00
if ( item . get ( 'minimized' ) === true ) {
/ * W h e n a c h a t i s m i n i m i z e d i n t r i m C h a t s , t r i m C h a t s n e e d s t o b e
* called again ( in case the minimized chats toggle is newly shown ) .
* /
this . trimChats ( ) ;
2014-06-11 22:53:14 +02:00
} else {
2015-05-31 14:50:11 +02:00
this . trimChats ( this . get ( item . get ( 'id' ) ) ) ;
2014-06-11 22:53:14 +02:00
}
2014-05-27 22:51:11 +02:00
} , this ) ;
2014-04-26 02:14:58 +02:00
} ,
2014-04-25 21:48:56 +02:00
2014-10-10 11:05:16 +02:00
_ensureElement : function ( ) {
2014-05-27 09:57:06 +02:00
/ * O v e r r i d e m e t h o d f r o m b a c k b o n e . j s
* If the # conversejs element doesn ' t exist , create it .
* /
2014-05-13 09:26:57 +02:00
if ( ! this . el ) {
var $el = $ ( '#conversejs' ) ;
if ( ! $el . length ) {
$el = $ ( '<div id="conversejs">' ) ;
$ ( 'body' ) . append ( $el ) ;
}
2014-06-08 21:43:00 +02:00
$el . html ( converse . templates . chats _panel ( ) ) ;
2014-05-13 09:26:57 +02:00
this . setElement ( $el , false ) ;
} else {
this . setElement ( _ . result ( this , 'el' ) , false ) ;
}
} ,
2014-09-12 15:23:21 +02:00
onChatBoxAdded : function ( item ) {
2014-05-31 21:26:18 +02:00
var view = this . get ( item . get ( 'id' ) ) ;
if ( ! view ) {
if ( item . get ( 'chatroom' ) ) {
view = new converse . ChatRoomView ( { 'model' : item } ) ;
} else if ( item . get ( 'box_id' ) === 'controlbox' ) {
2014-09-22 12:55:14 +02:00
view = new converse . ControlBoxView ( { model : item } ) ;
2014-05-31 21:26:18 +02:00
} else {
view = new converse . ChatBoxView ( { model : item } ) ;
}
this . add ( item . get ( 'id' ) , view ) ;
} else {
delete view . model ; // Remove ref to old model to help garbage collection
view . model = item ;
view . initialize ( ) ;
}
this . trimChats ( view ) ;
} ,
2014-06-08 21:43:00 +02:00
trimChats : function ( newchat ) {
/ * T h i s m e t h o d i s c a l l e d w h e n a n e w l y c r e a t e d c h a t b o x w i l l
* be shown .
2014-04-26 02:14:58 +02:00
*
2014-06-08 21:43:00 +02:00
* It checks whether there is enough space on the page to show
* another chat box . Otherwise it minimize the oldest chat box
* to create space .
2014-04-26 02:14:58 +02:00
* /
2014-06-08 21:43:00 +02:00
if ( converse . no _trimming || ( this . model . length <= 1 ) ) {
2014-05-27 19:18:02 +02:00
return ;
}
2014-06-14 18:21:52 +02:00
var oldest _chat ,
controlbox _width = 0 ,
2014-06-08 21:43:00 +02:00
$minimized = converse . minimized _chats . $el ,
2014-06-14 16:22:52 +02:00
minimized _width = _ . contains ( this . model . pluck ( 'minimized' ) , true ) ? $minimized . outerWidth ( true ) : 0 ,
2014-06-11 22:53:14 +02:00
boxes _width = newchat ? newchat . $el . outerWidth ( true ) : 0 ,
2014-06-14 16:22:52 +02:00
new _id = newchat ? newchat . model . get ( 'id' ) : null ,
2014-05-05 23:37:52 +02:00
controlbox = this . get ( 'controlbox' ) ;
2014-06-08 21:43:00 +02:00
2014-04-26 02:14:58 +02:00
if ( ! controlbox || ! controlbox . $el . is ( ':visible' ) ) {
2014-06-08 21:43:00 +02:00
controlbox _width = converse . controlboxtoggle . $el . outerWidth ( true ) ;
} else {
controlbox _width = controlbox . $el . outerWidth ( true ) ;
2014-04-26 02:14:58 +02:00
}
2014-06-08 21:43:00 +02:00
_ . each ( this . getAll ( ) , function ( view ) {
var id = view . model . get ( 'id' ) ;
2014-09-15 20:08:13 +02:00
if ( ( id !== 'controlbox' ) && ( id !== new _id ) && ( ! view . model . get ( 'minimized' ) ) && view . $el . is ( ':visible' ) ) {
2014-06-08 21:43:00 +02:00
boxes _width += view . $el . outerWidth ( true ) ;
2014-04-26 02:14:58 +02:00
}
} ) ;
2014-06-08 21:43:00 +02:00
2015-05-31 14:50:11 +02:00
if ( ( minimized _width + boxes _width + controlbox _width ) > $ ( 'body' ) . outerWidth ( true ) ) {
2014-06-14 18:21:52 +02:00
oldest _chat = this . getOldestMaximizedChat ( ) ;
2014-12-31 17:07:02 +01:00
if ( oldest _chat && oldest _chat . get ( 'id' ) !== new _id ) {
2014-06-14 18:21:52 +02:00
oldest _chat . minimize ( ) ;
}
2014-04-26 02:14:58 +02:00
}
2013-08-22 21:43:34 +02:00
} ,
2014-06-01 20:09:09 +02:00
getOldestMaximizedChat : function ( ) {
2014-05-27 18:34:22 +02:00
// Get oldest view (which is not controlbox)
var i = 0 ;
2014-05-27 22:51:11 +02:00
var model = this . model . sort ( ) . at ( i ) ;
2014-06-01 20:09:09 +02:00
while ( model . get ( 'id' ) === 'controlbox' || model . get ( 'minimized' ) === true ) {
2014-05-27 18:34:22 +02:00
i ++ ;
model = this . model . at ( i ) ;
2014-06-14 18:21:52 +02:00
if ( ! model ) {
return null ;
}
2014-05-27 18:34:22 +02:00
}
return model ;
} ,
2014-09-07 13:18:36 +02:00
closeAllChatBoxes : function ( include _controlbox ) {
// TODO: once Backbone.Overview has been refactored, we should
// be able to call .each on the views themselves.
2015-05-31 14:38:50 +02:00
var ids = [ ] ;
this . model . each ( function ( model ) {
2014-09-07 13:18:36 +02:00
var id = model . get ( 'id' ) ;
if ( include _controlbox || id !== 'controlbox' ) {
2015-05-31 14:38:50 +02:00
ids . push ( id ) ;
2014-09-07 13:18:36 +02:00
}
2015-05-31 14:38:50 +02:00
} ) ;
ids . forEach ( function ( id ) {
var chatbox = this . get ( id ) ;
if ( chatbox ) { chatbox . close ( ) ; }
} , this ) ;
2014-09-07 13:18:36 +02:00
return this ;
} ,
2014-05-27 18:34:22 +02:00
showChat : function ( attrs ) {
2015-01-01 21:25:12 +01:00
/ * F i n d t h e c h a t b o x a n d s h o w i t . I f i t d o e s n ' t e x i s t , c r e a t e i t .
2014-08-31 01:17:52 +02:00
* /
2013-08-22 21:43:34 +02:00
var chatbox = this . model . get ( attrs . jid ) ;
2014-10-12 10:34:33 +02:00
if ( ! chatbox ) {
2013-08-22 21:43:34 +02:00
chatbox = this . model . create ( attrs , {
'error' : function ( model , response ) {
converse . log ( response . responseText ) ;
}
} ) ;
}
2014-10-12 10:34:33 +02:00
if ( chatbox . get ( 'minimized' ) ) {
chatbox . maximize ( ) ;
} else {
chatbox . trigger ( 'show' ) ;
}
2013-08-22 21:43:34 +02:00
return chatbox ;
2013-06-02 00:21:06 +02:00
}
} ) ;
2014-06-01 20:09:09 +02:00
this . MinimizedChatBoxView = Backbone . View . extend ( {
2014-05-27 18:34:22 +02:00
tagName : 'div' ,
className : 'chat-head' ,
events : {
'click .close-chatbox-button' : 'close' ,
'click .restore-chat' : 'restore'
} ,
2014-06-04 09:32:40 +02:00
initialize : function ( ) {
2014-10-30 11:48:58 +01:00
this . model . messages . on ( 'add' , function ( m ) {
2015-01-09 10:48:36 +01:00
if ( m . get ( 'message' ) ) {
2014-10-30 11:48:58 +01:00
this . updateUnreadMessagesCounter ( ) ;
}
} , this ) ;
2014-06-04 09:32:40 +02:00
this . model . on ( 'change:minimized' , this . clearUnreadMessagesCounter , this ) ;
2014-10-30 11:48:58 +01:00
this . model . on ( 'showReceivedOTRMessage' , this . updateUnreadMessagesCounter , this ) ;
this . model . on ( 'showSentOTRMessage' , this . updateUnreadMessagesCounter , this ) ;
2014-06-04 09:32:40 +02:00
} ,
2014-05-27 09:57:06 +02:00
render : function ( ) {
2014-06-08 21:43:00 +02:00
var data = _ . extend (
this . model . toJSON ( ) ,
{ 'tooltip' : _ _ ( 'Click to restore this chat' ) }
) ;
2014-06-02 04:47:23 +02:00
if ( this . model . get ( 'chatroom' ) ) {
2014-06-04 09:32:40 +02:00
data . title = this . model . get ( 'name' ) ;
2014-06-02 04:47:23 +02:00
this . $el . addClass ( 'chat-head-chatroom' ) ;
} else {
2014-06-04 09:32:40 +02:00
data . title = this . model . get ( 'fullname' ) ;
2014-06-02 04:47:23 +02:00
this . $el . addClass ( 'chat-head-chatbox' ) ;
}
return this . $el . html ( converse . templates . trimmed _chat ( data ) ) ;
2014-05-27 09:57:06 +02:00
} ,
2014-06-04 09:32:40 +02:00
clearUnreadMessagesCounter : function ( ) {
2014-06-29 15:59:39 +02:00
this . model . set ( { 'num_unread' : 0 } ) ;
this . render ( ) ;
2014-06-04 09:32:40 +02:00
} ,
2014-06-29 15:59:39 +02:00
updateUnreadMessagesCounter : function ( ) {
2014-06-29 18:30:01 +02:00
this . model . set ( { 'num_unread' : this . model . get ( 'num_unread' ) + 1 } ) ;
2014-06-29 15:59:39 +02:00
this . render ( ) ;
2014-06-04 09:32:40 +02:00
} ,
2014-05-27 18:34:22 +02:00
close : function ( ev ) {
2014-09-22 12:55:14 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-06-14 19:47:19 +02:00
this . remove ( ) ;
2014-05-27 18:34:22 +02:00
this . model . destroy ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxClosed' , this ) ;
2014-05-27 18:34:22 +02:00
return this ;
2014-05-27 09:57:06 +02:00
} ,
2014-05-27 18:34:22 +02:00
2014-10-10 11:05:16 +02:00
restore : _ . debounce ( function ( ev ) {
2015-01-01 21:25:12 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-11-13 10:56:52 +01:00
this . model . messages . off ( 'add' , null , this ) ;
2014-06-14 18:21:52 +02:00
this . remove ( ) ;
2014-06-08 21:43:00 +02:00
this . model . maximize ( ) ;
2014-12-11 12:52:51 +01:00
} , 200 , true )
2014-05-27 09:57:06 +02:00
} ) ;
2014-06-08 21:43:00 +02:00
this . MinimizedChats = Backbone . Overview . extend ( {
el : "#minimized-chats" ,
events : {
"click #toggle-minimized-chats" : "toggle"
} ,
2014-05-27 09:57:06 +02:00
initialize : function ( ) {
2014-06-14 20:32:45 +02:00
this . initToggle ( ) ;
2014-06-21 23:01:56 +02:00
this . model . on ( "add" , this . onChanged , this ) ;
2014-06-14 19:47:19 +02:00
this . model . on ( "destroy" , this . removeChat , this ) ;
this . model . on ( "change:minimized" , this . onChanged , this ) ;
2014-06-29 15:59:39 +02:00
this . model . on ( 'change:num_unread' , this . updateUnreadMessagesCounter , this ) ;
2014-05-27 09:57:06 +02:00
} ,
2014-09-20 15:07:55 +02:00
tearDown : function ( ) {
this . model . off ( "add" , this . onChanged ) ;
this . model . off ( "destroy" , this . removeChat ) ;
this . model . off ( "change:minimized" , this . onChanged ) ;
this . model . off ( 'change:num_unread' , this . updateUnreadMessagesCounter ) ;
return this ;
} ,
2014-06-14 20:32:45 +02:00
initToggle : function ( ) {
this . toggleview = new converse . MinimizedChatsToggleView ( {
model : new converse . MinimizedChatsToggle ( )
} ) ;
2014-06-30 18:53:58 +02:00
var id = b64 _sha1 ( 'converse.minchatstoggle' + converse . bare _jid ) ;
this . toggleview . model . id = id ; // Appears to be necessary for backbone.browserStorage
this . toggleview . model . browserStorage = new Backbone . BrowserStorage [ converse . storage ] ( id ) ;
2014-06-14 22:03:04 +02:00
this . toggleview . model . fetch ( ) ;
2014-06-14 20:32:45 +02:00
} ,
2014-05-27 09:57:06 +02:00
render : function ( ) {
2014-06-08 21:43:00 +02:00
if ( this . keys ( ) . length === 0 ) {
this . $el . hide ( 'fast' ) ;
} else if ( this . keys ( ) . length === 1 ) {
this . $el . show ( 'fast' ) ;
}
2014-05-27 09:57:06 +02:00
return this . $el ;
} ,
2014-06-14 20:32:45 +02:00
toggle : function ( ev ) {
2014-08-01 20:09:32 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-06-21 23:01:56 +02:00
this . toggleview . model . save ( { 'collapsed' : ! this . toggleview . model . get ( 'collapsed' ) } ) ;
2014-06-08 21:43:00 +02:00
this . $ ( '.minimized-chats-flyout' ) . toggle ( ) ;
2014-05-27 09:57:06 +02:00
} ,
2014-06-01 17:57:03 +02:00
onChanged : function ( item ) {
2014-06-21 23:01:56 +02:00
if ( item . get ( 'id' ) !== 'controlbox' && item . get ( 'minimized' ) ) {
2014-06-01 20:09:09 +02:00
this . addChat ( item ) ;
2014-06-21 23:01:56 +02:00
} else if ( this . get ( item . get ( 'id' ) ) ) {
2014-06-08 21:43:00 +02:00
this . removeChat ( item ) ;
2014-06-01 17:57:03 +02:00
}
2014-06-01 20:09:09 +02:00
} ,
addChat : function ( item ) {
2014-06-14 18:21:52 +02:00
var existing = this . get ( item . get ( 'id' ) ) ;
if ( existing && existing . $el . parent ( ) . length !== 0 ) {
2014-06-08 21:43:00 +02:00
return ;
}
2014-06-01 20:09:09 +02:00
var view = new converse . MinimizedChatBoxView ( { model : item } ) ;
2014-06-08 21:43:00 +02:00
this . $ ( '.minimized-chats-flyout' ) . append ( view . render ( ) ) ;
2014-06-01 20:09:09 +02:00
this . add ( item . get ( 'id' ) , view ) ;
2014-06-14 19:44:00 +02:00
this . toggleview . model . set ( { 'num_minimized' : this . keys ( ) . length } ) ;
2014-06-08 21:43:00 +02:00
this . render ( ) ;
} ,
removeChat : function ( item ) {
2014-06-14 16:22:52 +02:00
this . remove ( item . get ( 'id' ) ) ;
2014-06-14 19:44:00 +02:00
this . toggleview . model . set ( { 'num_minimized' : this . keys ( ) . length } ) ;
2014-06-08 21:43:00 +02:00
this . render ( ) ;
2014-06-29 15:59:39 +02:00
} ,
updateUnreadMessagesCounter : function ( ) {
2014-08-03 23:02:25 +02:00
var ls = this . model . pluck ( 'num_unread' ) ,
count = 0 , i ;
2014-06-29 15:59:39 +02:00
for ( i = 0 ; i < ls . length ; i ++ ) { count += ls [ i ] ; }
this . toggleview . model . set ( { 'num_unread' : count } ) ;
this . render ( ) ;
2014-06-08 21:43:00 +02:00
}
} ) ;
this . MinimizedChatsToggle = Backbone . Model . extend ( {
initialize : function ( ) {
this . set ( {
2014-06-14 20:32:45 +02:00
'collapsed' : this . get ( 'collapsed' ) || false ,
2014-06-29 15:59:39 +02:00
'num_minimized' : this . get ( 'num_minimized' ) || 0 ,
2014-10-26 15:52:27 +01:00
'num_unread' : this . get ( 'num_unread' ) || 0
2014-06-08 21:43:00 +02:00
} ) ;
2014-06-01 17:57:03 +02:00
}
2014-06-08 21:43:00 +02:00
} ) ;
this . MinimizedChatsToggleView = Backbone . View . extend ( {
el : '#toggle-minimized-chats' ,
initialize : function ( ) {
this . model . on ( 'change:num_minimized' , this . render , this ) ;
2014-06-29 18:30:01 +02:00
this . model . on ( 'change:num_unread' , this . render , this ) ;
2014-06-14 20:32:45 +02:00
this . $flyout = this . $el . siblings ( '.minimized-chats-flyout' ) ;
2014-06-08 21:43:00 +02:00
} ,
2014-06-01 20:09:09 +02:00
2014-06-08 21:43:00 +02:00
render : function ( ) {
2014-06-14 20:32:45 +02:00
this . $el . html ( converse . templates . toggle _chats (
2014-06-29 15:59:39 +02:00
_ . extend ( this . model . toJSON ( ) , {
'Minimized' : _ _ ( 'Minimized' )
} )
2014-06-14 20:32:45 +02:00
) ) ;
if ( this . model . get ( 'collapsed' ) ) {
this . $flyout . hide ( ) ;
} else {
this . $flyout . show ( ) ;
}
2014-06-08 21:43:00 +02:00
return this . $el ;
2014-10-26 15:52:27 +01:00
}
2014-05-27 09:57:06 +02:00
} ) ;
2014-08-02 11:35:03 +02:00
this . RosterContact = Backbone . Model . extend ( {
2013-06-02 00:21:06 +02:00
initialize : function ( attributes , options ) {
var jid = attributes . jid ;
2015-05-18 11:42:29 +02:00
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
2015-07-14 10:27:40 +02:00
var resource = Strophe . getResourceFromJid ( jid ) ;
2015-05-18 11:42:29 +02:00
attributes . jid = bare _jid ;
2015-04-06 11:10:05 +02:00
this . set ( _ . extend ( {
2015-05-18 11:42:29 +02:00
'id' : bare _jid ,
'jid' : bare _jid ,
'fullname' : bare _jid ,
2014-09-02 16:42:08 +02:00
'chat_status' : 'offline' ,
2013-06-02 00:21:06 +02:00
'user_id' : Strophe . getNodeFromJid ( jid ) ,
2015-07-14 10:27:40 +02:00
'resources' : resource ? [ resource ] : [ ] ,
2014-07-29 19:53:57 +02:00
'groups' : [ ] ,
2014-12-07 13:44:27 +01:00
'image_type' : 'image/png' ,
'image' : "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg==" ,
2013-06-02 00:21:06 +02:00
'status' : ''
2015-04-06 11:10:05 +02:00
} , attributes ) ) ;
2015-04-10 13:04:00 +02:00
this . on ( 'destroy' , function ( ) { this . removeFromRoster ( ) ; } . bind ( this ) ) ;
2015-04-06 11:10:05 +02:00
} ,
subscribe : function ( message ) {
/ * S e n d a p r e s e n c e s u b s c r i p t i o n r e q u e s t t o t h i s r o s t e r c o n t a c t
2015-07-04 10:10:21 +02:00
*
2015-04-06 13:41:48 +02:00
* Parameters :
* ( String ) message - An optional message to explain the
* reason for the subscription request .
2015-04-06 11:10:05 +02:00
* /
this . save ( 'ask' , "subscribe" ) ; // ask === 'subscribe' Means we have ask to subscribe to them.
var pres = $pres ( { to : this . get ( 'jid' ) , type : "subscribe" } ) ;
if ( message && message !== "" ) {
pres . c ( "status" ) . t ( message ) . up ( ) ;
}
var nick = converse . xmppstatus . get ( 'fullname' ) ;
if ( nick && nick !== "" ) {
pres . c ( 'nick' , { 'xmlns' : Strophe . NS . NICK } ) . t ( nick ) . up ( ) ;
}
converse . connection . send ( pres ) ;
2015-04-06 13:41:48 +02:00
return this ;
2015-04-06 11:10:05 +02:00
} ,
2015-04-10 13:01:31 +02:00
ackSubscribe : function ( ) {
2015-04-07 20:04:27 +02:00
/ * U p o n r e c e i v i n g t h e p r e s e n c e s t a n z a o f t y p e " s u b s c r i b e d " ,
* the user SHOULD acknowledge receipt of that subscription
* state notification by sending a presence stanza of type
* "subscribe" to the contact
* /
converse . connection . send ( $pres ( {
'type' : 'subscribe' ,
'to' : this . get ( 'jid' )
} ) ) ;
} ,
2015-04-10 13:01:31 +02:00
ackUnsubscribe : function ( jid ) {
/ * U p o n r e c e i v i n g t h e p r e s e n c e s t a n z a o f t y p e " u n s u b s c r i b e d " ,
* the user SHOULD acknowledge receipt of that subscription state
* notification by sending a presence stanza of type "unsubscribe"
* this step lets the user ' s server know that it MUST no longer
* send notification of the subscription state change to the user .
* Parameters :
* ( String ) jid - The Jabber ID of the user who is unsubscribing
* /
converse . connection . send ( $pres ( { 'type' : 'unsubscribe' , 'to' : this . get ( 'jid' ) } ) ) ;
this . destroy ( ) ; // Will cause removeFromRoster to be called.
} ,
2015-04-06 11:10:05 +02:00
unauthorize : function ( message ) {
/ * U n a u t h o r i z e t h i s c o n t a c t ' s p r e s e n c e s u b s c r i p t i o n
* Parameters :
2015-04-06 12:26:08 +02:00
* ( String ) message - Optional message to send to the person being unauthorized
2015-04-06 11:10:05 +02:00
* /
2015-04-06 12:26:08 +02:00
converse . rejectPresenceSubscription ( this . get ( 'jid' ) , message ) ;
2015-04-06 11:10:05 +02:00
return this ;
} ,
2015-04-06 12:26:08 +02:00
authorize : function ( message ) {
/ * A u t h o r i z e p r e s e n c e s u b s c r i p t i o n
* Parameters :
* ( String ) message - Optional message to send to the person being authorized
* /
2015-04-07 18:23:45 +02:00
var pres = $pres ( { to : this . get ( 'jid' ) , type : "subscribed" } ) ;
2015-04-06 12:26:08 +02:00
if ( message && message !== "" ) {
pres . c ( "status" ) . t ( message ) ;
}
converse . connection . send ( pres ) ;
2015-04-06 13:41:48 +02:00
return this ;
2015-04-06 12:26:08 +02:00
} ,
2015-04-06 20:55:38 +02:00
removeResource : function ( resource ) {
2015-04-07 18:23:45 +02:00
var resources = this . get ( 'resources' ) , idx ;
if ( resource ) {
idx = _ . indexOf ( resources , resource ) ;
if ( idx !== - 1 ) {
resources . splice ( idx , 1 ) ;
this . save ( { 'resources' : resources } ) ;
}
2015-04-06 20:55:38 +02:00
}
2015-04-07 18:23:45 +02:00
return resources . length ;
2015-04-06 20:55:38 +02:00
} ,
2015-04-06 11:10:05 +02:00
removeFromRoster : function ( callback ) {
/ * I n s t r u c t t h e X M P P s e r v e r t o r e m o v e t h i s c o n t a c t f r o m o u r r o s t e r
* Parameters :
* ( Function ) callback
* /
var iq = $iq ( { type : 'set' } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } )
. c ( 'item' , { jid : this . get ( 'jid' ) , subscription : "remove" } ) ;
converse . connection . sendIQ ( iq , callback , callback ) ;
2015-04-06 13:52:58 +02:00
return this ;
2014-10-26 23:10:43 +01:00
} ,
showInRoster : function ( ) {
2014-11-14 19:42:27 +01:00
var chatStatus = this . get ( 'chat_status' ) ;
2014-12-20 10:17:20 +01:00
if ( ( converse . show _only _online _users && chatStatus !== 'online' ) || ( converse . hide _offline _users && chatStatus === 'offline' ) ) {
2014-12-11 01:32:59 +01:00
// If pending or requesting, show
2015-04-07 20:04:27 +02:00
if ( ( this . get ( 'ask' ) === 'subscribe' ) ||
( this . get ( 'subscription' ) === 'from' ) ||
( this . get ( 'requesting' ) === true ) ) {
2014-12-11 01:32:59 +01:00
return true ;
}
2014-11-14 19:42:27 +01:00
return false ;
2014-12-11 01:32:59 +01:00
}
2014-11-14 19:42:27 +01:00
return true ;
2013-06-02 00:21:06 +02:00
}
} ) ;
2014-08-02 11:35:03 +02:00
this . RosterContactView = Backbone . View . extend ( {
2013-06-02 00:21:06 +02:00
tagName : 'dd' ,
events : {
"click .accept-xmpp-request" : "acceptRequest" ,
"click .decline-xmpp-request" : "declineRequest" ,
"click .open-chat" : "openChat" ,
"click .remove-xmpp-contact" : "removeContact"
} ,
2014-07-31 21:50:34 +02:00
initialize : function ( ) {
2014-10-26 23:10:43 +01:00
this . model . on ( "change" , this . render , this ) ;
2014-08-01 20:09:32 +02:00
this . model . on ( "remove" , this . remove , this ) ;
this . model . on ( "destroy" , this . remove , this ) ;
this . model . on ( "open" , this . openChat , this ) ;
2014-07-31 21:50:34 +02:00
} ,
2013-06-02 00:21:06 +02:00
render : function ( ) {
2014-10-26 23:10:43 +01:00
if ( ! this . model . showInRoster ( ) ) {
this . $el . hide ( ) ;
return this ;
} else if ( this . $el [ 0 ] . style . display === "none" ) {
this . $el . show ( ) ;
}
2013-06-02 00:21:06 +02:00
var item = this . model ,
ask = item . get ( 'ask' ) ,
2014-07-19 19:14:29 +02:00
chat _status = item . get ( 'chat_status' ) ,
2013-11-06 08:54:58 +01:00
requesting = item . get ( 'requesting' ) ,
2013-06-02 00:21:06 +02:00
subscription = item . get ( 'subscription' ) ;
2013-09-26 21:45:42 +02:00
var classes _to _remove = [
'current-xmpp-contact' ,
'pending-xmpp-contact' ,
'requesting-xmpp-contact'
2013-10-05 22:34:47 +02:00
] . concat ( _ . keys ( STATUSES ) ) ;
2013-09-26 21:45:42 +02:00
_ . each ( classes _to _remove ,
function ( cls ) {
if ( this . el . className . indexOf ( cls ) !== - 1 ) {
this . $el . removeClass ( cls ) ;
}
} , this ) ;
2014-07-19 19:14:29 +02:00
this . $el . addClass ( chat _status ) . data ( 'status' , chat _status ) ;
2013-06-02 00:21:06 +02:00
2014-07-23 19:11:29 +02:00
if ( ( ask === 'subscribe' ) || ( subscription === 'from' ) ) {
2014-07-23 20:10:10 +02:00
/ * a s k = = = ' s u b s c r i b e '
* Means we have asked to subscribe to them .
*
* subscription === 'from'
* They are subscribed to use , but not vice versa .
* We assume that there is a pending subscription
* from us to them ( otherwise we ' re in a state not
* supported by converse . js ) .
*
* So in both cases the user is a "pending" contact .
* /
2013-06-02 00:21:06 +02:00
this . $el . addClass ( 'pending-xmpp-contact' ) ;
2014-01-19 06:10:26 +01:00
this . $el . html ( converse . templates . pending _contact (
_ . extend ( item . toJSON ( ) , {
2015-06-18 16:31:30 +02:00
'desc_remove' : _ _ ( 'Click to remove this contact' ) ,
'allow_chat_pending_contacts' : converse . allow _chat _pending _contacts
2014-01-19 06:10:26 +01:00
} )
) ) ;
2013-11-06 08:54:58 +01:00
} else if ( requesting === true ) {
2013-06-02 00:21:06 +02:00
this . $el . addClass ( 'requesting-xmpp-contact' ) ;
2014-01-19 06:10:26 +01:00
this . $el . html ( converse . templates . requesting _contact (
_ . extend ( item . toJSON ( ) , {
2014-07-19 14:50:25 +02:00
'desc_accept' : _ _ ( "Click to accept this contact request" ) ,
2015-06-18 16:31:30 +02:00
'desc_decline' : _ _ ( "Click to decline this contact request" ) ,
'allow_chat_pending_contacts' : converse . allow _chat _pending _contacts
2014-01-19 06:10:26 +01:00
} )
) ) ;
2013-10-20 22:20:45 +02:00
converse . controlboxtoggle . showControlBox ( ) ;
2013-10-05 22:16:09 +02:00
} else if ( subscription === 'both' || subscription === 'to' ) {
2013-06-02 00:21:06 +02:00
this . $el . addClass ( 'current-xmpp-contact' ) ;
2015-04-09 10:35:12 +02:00
this . $el . removeClass ( _ . without ( [ 'both' , 'to' ] , subscription ) [ 0 ] ) . addClass ( subscription ) ;
2014-01-19 05:21:49 +01:00
this . $el . html ( converse . templates . roster _item (
_ . extend ( item . toJSON ( ) , {
2014-07-19 19:14:29 +02:00
'desc_status' : STATUSES [ chat _status || 'offline' ] ,
2014-01-19 05:21:49 +01:00
'desc_chat' : _ _ ( 'Click to chat with this contact' ) ,
2015-03-09 19:47:01 +01:00
'desc_remove' : _ _ ( 'Click to remove this contact' ) ,
2015-05-08 19:08:09 +02:00
'title_fullname' : _ _ ( 'Name' ) ,
2015-03-09 19:47:01 +01:00
'allow_contact_removal' : converse . allow _contact _removal
2014-01-19 05:21:49 +01:00
} )
2013-10-05 22:16:09 +02:00
) ) ;
2013-06-02 00:21:06 +02:00
}
return this ;
2014-11-15 14:01:16 +01:00
} ,
openChat : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2015-01-01 21:25:12 +01:00
return converse . chatboxviews . showChat ( this . model . attributes ) ;
2014-11-15 14:01:16 +01:00
} ,
removeContact : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2015-03-09 19:47:01 +01:00
if ( ! converse . allow _contact _removal ) { return ; }
2014-11-15 14:01:16 +01:00
var result = confirm ( _ _ ( "Are you sure you want to remove this contact?" ) ) ;
if ( result === true ) {
2015-04-06 11:10:05 +02:00
var iq = $iq ( { type : 'set' } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } )
. c ( 'item' , { jid : this . model . get ( 'jid' ) , subscription : "remove" } ) ;
converse . connection . sendIQ ( iq ,
function ( iq ) {
2015-04-10 16:30:04 +02:00
this . model . destroy ( ) ;
2015-04-06 11:10:05 +02:00
this . remove ( ) ;
} . bind ( this ) ,
function ( err ) {
alert ( _ _ ( "Sorry, there was an error while trying to remove " + name + " as a contact." ) ) ;
converse . log ( err ) ;
}
) ;
2014-11-15 14:01:16 +01:00
}
} ,
acceptRequest : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2015-04-06 13:43:27 +02:00
converse . roster . sendContactAddIQ (
this . model . get ( 'jid' ) ,
this . model . get ( 'fullname' ) ,
[ ] ,
2015-04-08 10:35:14 +02:00
function ( ) { this . model . authorize ( ) . subscribe ( ) ; } . bind ( this )
2015-04-06 13:43:27 +02:00
) ;
2014-11-15 14:01:16 +01:00
} ,
declineRequest : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var result = confirm ( _ _ ( "Are you sure you want to decline this contact request?" ) ) ;
if ( result === true ) {
2015-04-06 12:26:08 +02:00
this . model . unauthorize ( ) . destroy ( ) ;
2014-11-15 14:01:16 +01:00
}
return this ;
2013-10-03 16:05:21 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2014-08-02 11:35:03 +02:00
this . RosterContacts = Backbone . Collection . extend ( {
model : converse . RosterContact ,
2014-08-02 15:05:27 +02:00
comparator : function ( contact1 , contact2 ) {
2014-10-25 12:33:24 +02:00
var name1 , name2 ;
2014-08-02 15:05:27 +02:00
var status1 = contact1 . get ( 'chat_status' ) || 'offline' ;
var status2 = contact2 . get ( 'chat_status' ) || 'offline' ;
if ( STATUS _WEIGHTS [ status1 ] === STATUS _WEIGHTS [ status2 ] ) {
2014-10-25 12:33:24 +02:00
name1 = contact1 . get ( 'fullname' ) . toLowerCase ( ) ;
name2 = contact2 . get ( 'fullname' ) . toLowerCase ( ) ;
2014-08-02 15:05:27 +02:00
return name1 < name2 ? - 1 : ( name1 > name2 ? 1 : 0 ) ;
} else {
return STATUS _WEIGHTS [ status1 ] < STATUS _WEIGHTS [ status2 ] ? - 1 : 1 ;
2014-08-03 23:07:48 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
subscribeToSuggestedItems : function ( msg ) {
2014-10-27 18:48:54 +01:00
$ ( msg ) . find ( 'item' ) . each ( function ( i , items ) {
2015-04-07 20:04:27 +02:00
if ( this . getAttribute ( 'action' ) === 'add' ) {
converse . roster . addAndSubscribe (
this . getAttribute ( 'jid' ) , null , converse . xmppstatus . get ( 'fullname' ) ) ;
2013-06-02 00:21:06 +02:00
}
} ) ;
return true ;
} ,
isSelf : function ( jid ) {
return ( Strophe . getBareJidFromJid ( jid ) === Strophe . getBareJidFromJid ( converse . connection . jid ) ) ;
} ,
2015-04-06 13:41:48 +02:00
addAndSubscribe : function ( jid , name , groups , message , attributes ) {
2015-04-06 11:10:05 +02:00
/ * A d d a r o s t e r c o n t a c t a n d t h e n o n c e w e h a v e c o n f i r m a t i o n f r o m
2015-04-06 13:41:48 +02:00
* the XMPP server we subscribe to that contact ' s presence updates .
* Parameters :
* ( String ) jid - The Jabber ID of the user being added and subscribed to .
* ( String ) name - The name of that user
* ( Array of Strings ) groups - Any roster groups the user might belong to
* ( String ) message - An optional message to explain the
* reason for the subscription request .
* ( Object ) attributes - Any additional attributes to be stored on the user ' s model .
2015-04-06 11:10:05 +02:00
* /
2015-04-06 13:41:48 +02:00
this . addContact ( jid , name , groups , attributes ) . done ( function ( contact ) {
2015-04-06 11:10:05 +02:00
if ( contact instanceof converse . RosterContact ) {
contact . subscribe ( message ) ;
}
} ) ;
} ,
2015-04-06 13:43:27 +02:00
sendContactAddIQ : function ( jid , name , groups , callback , errback ) {
/ * S e n d a n I Q s t a n z a t o t h e X M P P s e r v e r t o a d d a n e w r o s t e r c o n t a c t .
* Parameters :
* ( String ) jid - The Jabber ID of the user being added
* ( String ) name - The name of that user
* ( Array of Strings ) groups - Any roster groups the user might belong to
* ( Function ) callback - A function to call once the VCard is returned
* ( Function ) errback - A function to call if an error occured
* /
name = _ . isEmpty ( name ) ? jid : name ;
var iq = $iq ( { type : 'set' } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } )
. c ( 'item' , { jid : jid , name : name } ) ;
_ . map ( groups , function ( group ) { iq . c ( 'group' ) . t ( group ) . up ( ) ; } ) ;
converse . connection . sendIQ ( iq , callback , errback ) ;
} ,
2015-04-06 13:41:48 +02:00
addContact : function ( jid , name , groups , attributes ) {
2015-04-06 13:43:27 +02:00
/ * A d d s a R o s t e r C o n t a c t i n s t a n c e t o c o n v e r s e . r o s t e r a n d
* registers the contact on the XMPP server .
2015-04-06 11:10:05 +02:00
* Returns a promise which is resolved once the XMPP server has
* responded .
2015-04-06 13:41:48 +02:00
* Parameters :
* ( String ) jid - The Jabber ID of the user being added and subscribed to .
* ( String ) name - The name of that user
* ( Array of Strings ) groups - Any roster groups the user might belong to
* ( Object ) attributes - Any additional attributes to be stored on the user ' s model .
2015-04-06 11:10:05 +02:00
* /
var deferred = new $ . Deferred ( ) ;
groups = groups || [ ] ;
name = _ . isEmpty ( name ) ? jid : name ;
2015-04-06 13:43:27 +02:00
this . sendContactAddIQ ( jid , name , groups ,
2015-04-06 11:10:05 +02:00
function ( iq ) {
2015-04-06 13:41:48 +02:00
var contact = this . create ( _ . extend ( {
2015-04-06 11:10:05 +02:00
ask : undefined ,
fullname : name ,
groups : groups ,
jid : jid ,
requesting : false ,
2015-04-06 13:41:48 +02:00
subscription : 'none'
} , attributes ) , { sort : false } ) ;
2015-04-06 11:10:05 +02:00
deferred . resolve ( contact ) ;
} . bind ( this ) ,
function ( err ) {
alert ( _ _ ( "Sorry, there was an error while trying to add " + name + " as a contact." ) ) ;
converse . log ( err ) ;
deferred . resolve ( err ) ;
}
) ;
return deferred . promise ( ) ;
} ,
2013-06-02 00:21:06 +02:00
addResource : function ( bare _jid , resource ) {
2013-10-08 08:45:17 +02:00
var item = this . get ( bare _jid ) ,
2013-06-02 00:21:06 +02:00
resources ;
if ( item ) {
resources = item . get ( 'resources' ) ;
if ( resources ) {
2015-10-25 18:49:35 +01:00
if ( _ . indexOf ( resources , resource ) === - 1 ) {
2013-06-02 00:21:06 +02:00
resources . push ( resource ) ;
item . set ( { 'resources' : resources } ) ;
}
} else {
item . set ( { 'resources' : [ resource ] } ) ;
}
}
} ,
2015-04-06 13:41:48 +02:00
subscribeBack : function ( bare _jid ) {
var contact = this . get ( bare _jid ) ;
if ( contact instanceof converse . RosterContact ) {
contact . authorize ( ) . subscribe ( ) ;
2013-05-30 21:24:00 +02:00
} else {
2015-06-22 18:55:02 +02:00
// Can happen when a subscription is retried or roster was deleted
2015-06-16 23:03:57 +02:00
this . addContact ( bare _jid , '' , [ ] , { 'subscription' : 'from' } ) . done ( function ( contact ) {
2015-04-06 13:41:48 +02:00
if ( contact instanceof converse . RosterContact ) {
contact . authorize ( ) . subscribe ( ) ;
}
2013-06-02 00:21:06 +02:00
} ) ;
}
} ,
getNumOnlineContacts : function ( ) {
var count = 0 ,
2013-12-18 15:48:02 +01:00
ignored = [ 'offline' , 'unavailable' ] ,
2013-06-02 00:21:06 +02:00
models = this . models ,
models _length = models . length ,
i ;
2013-12-18 15:48:02 +01:00
if ( converse . show _only _online _users ) {
ignored = _ . union ( ignored , [ 'dnd' , 'xa' , 'away' ] ) ;
}
2013-06-02 00:21:06 +02:00
for ( i = 0 ; i < models _length ; i ++ ) {
2013-12-18 15:48:02 +01:00
if ( _ . indexOf ( ignored , models [ i ] . get ( 'chat_status' ) ) === - 1 ) {
2013-06-02 00:21:06 +02:00
count ++ ;
2013-05-30 21:24:00 +02:00
}
}
2013-06-02 00:21:06 +02:00
return count ;
} ,
2015-04-06 20:55:38 +02:00
onRosterPush : function ( iq ) {
/ * H a n d l e r o s t e r u p d a t e s f r o m t h e X M P P s e r v e r .
* See : https : //xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
2015-07-19 13:43:26 +02:00
*
2015-04-06 20:55:38 +02:00
* Parameters :
* ( XMLElement ) IQ - The IQ stanza received from the XMPP server .
* /
var id = iq . getAttribute ( 'id' ) ;
var from = iq . getAttribute ( 'from' ) ;
2015-11-04 18:57:44 +01:00
if ( from && from !== "" && Strophe . getBareJidFromJid ( from ) !== converse . bare _jid ) {
2015-04-06 20:55:38 +02:00
// Receiving client MUST ignore stanza unless it has no from or from = user's bare JID.
2015-11-04 18:57:44 +01:00
// XXX: Some naughty servers apparently send from a full
// JID so we need to explicitly compare bare jids here.
// https://github.com/jcbrand/converse.js/issues/493
2015-04-06 20:55:38 +02:00
converse . connection . send (
$iq ( { type : 'error' , id : id , from : converse . connection . jid } )
. c ( 'error' , { 'type' : 'cancel' } )
. c ( 'service-unavailable' , { 'xmlns' : Strophe . NS . ROSTER } )
) ;
return true ;
}
converse . connection . send ( $iq ( { type : 'result' , id : id , from : converse . connection . jid } ) ) ;
$ ( iq ) . children ( 'query' ) . find ( 'item' ) . each ( function ( idx , item ) {
this . updateContact ( item ) ;
} . bind ( this ) ) ;
2015-10-19 15:52:49 +02:00
converse . emit ( 'rosterPush' , iq ) ;
2015-11-04 18:57:44 +01:00
return true ;
2013-06-02 00:21:06 +02:00
} ,
2013-05-30 21:24:00 +02:00
2015-04-06 20:55:38 +02:00
fetchFromServer : function ( callback , errback ) {
/* Get the roster from the XMPP server */
var iq = $iq ( { type : 'get' , 'id' : converse . connection . getUniqueId ( 'roster' ) } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } ) ;
return converse . connection . sendIQ ( iq , this . onReceivedFromServer . bind ( this ) ) ;
} ,
2013-10-15 18:29:16 +02:00
2015-04-06 20:55:38 +02:00
onReceivedFromServer : function ( iq ) {
/ * A n I Q s t a n z a c o n t a i n i n g t h e r o s t e r h a s b e e n r e c e i v e d f r o m
* the XMPP server .
* /
converse . emit ( 'roster' , iq ) ;
$ ( iq ) . children ( 'query' ) . find ( 'item' ) . each ( function ( idx , item ) {
this . updateContact ( item ) ;
} . bind ( this ) ) ;
2013-10-15 18:29:16 +02:00
if ( ! converse . initial _presence _sent ) {
/ * O n c e w e ' v e s e n t o u t o u r i n i t i a l p r e s e n c e s t a n z a , w e ' l l
* start receiving presence stanzas from our contacts .
* We therefore only want to do this after our roster has
* been set up ( otherwise we can ' t meaningfully process
* incoming presence stanzas ) .
* /
converse . initial _presence _sent = 1 ;
converse . xmppstatus . sendPresence ( ) ;
}
2013-06-02 00:21:06 +02:00
} ,
2013-05-30 21:24:00 +02:00
2015-04-06 20:55:38 +02:00
updateContact : function ( item ) {
/ * U p d a t e o r c r e a t e R o s t e r C o n t a c t m o d e l s b a s e d o n i t e m s
* received in the IQ from the server .
* /
var jid = item . getAttribute ( 'jid' ) ;
if ( this . isSelf ( jid ) ) { return ; }
var groups = [ ] ,
contact = this . get ( jid ) ,
ask = item . getAttribute ( "ask" ) ,
subscription = item . getAttribute ( "subscription" ) ;
$ . map ( item . getElementsByTagName ( 'group' ) , function ( group ) {
groups . push ( Strophe . getText ( group ) ) ;
} ) ;
if ( ! contact ) {
2015-04-10 13:01:31 +02:00
if ( ( subscription === "none" && ask === null ) || ( subscription === "remove" ) ) {
2015-04-07 20:04:27 +02:00
return ; // We're lazy when adding contacts.
}
2015-04-06 20:55:38 +02:00
this . create ( {
ask : ask ,
fullname : item . getAttribute ( "name" ) || jid ,
groups : groups ,
jid : jid ,
subscription : subscription
} , { sort : false } ) ;
} else {
2015-04-07 20:04:27 +02:00
if ( subscription === "remove" ) {
return contact . destroy ( ) ; // will trigger removeFromRoster
2015-04-06 20:55:38 +02:00
}
2015-04-07 20:04:27 +02:00
// We only find out about requesting contacts via the
// presence handler, so if we receive a contact
// here, we know they aren't requesting anymore.
// see docs/DEVELOPER.rst
contact . save ( {
subscription : subscription ,
ask : ask ,
requesting : null ,
groups : groups
} ) ;
2015-04-06 20:55:38 +02:00
}
} ,
2015-04-06 13:41:48 +02:00
createContactFromVCard : function ( iq , jid , fullname , img , img _type , url ) {
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
this . create ( {
jid : bare _jid ,
subscription : 'none' ,
ask : null ,
requesting : true ,
fullname : fullname || bare _jid ,
image : img ,
image _type : img _type ,
url : url ,
vcard _updated : moment ( ) . format ( )
} ) ;
} ,
2013-10-05 16:46:57 +02:00
handleIncomingSubscription : function ( jid ) {
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
2015-04-06 13:41:48 +02:00
var contact = this . get ( bare _jid ) ;
2013-10-05 16:46:57 +02:00
if ( ! converse . allow _contact _requests ) {
2015-04-06 12:26:08 +02:00
converse . rejectPresenceSubscription ( jid , _ _ ( "This client does not allow presence subscriptions" ) ) ;
2013-10-05 16:46:57 +02:00
}
if ( converse . auto _subscribe ) {
2015-10-25 18:49:35 +01:00
if ( ( ! contact ) || ( contact . get ( 'subscription' ) !== 'to' ) ) {
2015-04-06 13:41:48 +02:00
this . subscribeBack ( bare _jid ) ;
2013-10-05 16:46:57 +02:00
} else {
2015-04-06 13:41:48 +02:00
contact . authorize ( ) ;
2013-10-05 16:46:57 +02:00
}
} else {
2015-04-07 20:04:27 +02:00
if ( contact ) {
2015-10-25 18:49:35 +01:00
if ( contact . get ( 'subscription' ) !== 'none' ) {
2015-04-07 20:04:27 +02:00
contact . authorize ( ) ;
2015-10-25 18:49:35 +01:00
} else if ( contact . get ( 'ask' ) === "subscribe" ) {
2015-04-07 20:04:27 +02:00
contact . authorize ( ) ;
}
2015-04-06 13:41:48 +02:00
} else if ( ! contact ) {
converse . getVCard (
bare _jid , this . createContactFromVCard . bind ( this ) ,
function ( iq , jid ) {
converse . log ( "Error while retrieving vcard for " + jid ) ;
2015-04-07 18:23:45 +02:00
this . createContactFromVCard . call ( this , iq , jid ) ;
2015-04-06 13:41:48 +02:00
} . bind ( this )
) ;
2013-10-05 16:46:57 +02:00
}
}
} ,
2013-06-02 00:21:06 +02:00
presenceHandler : function ( presence ) {
var $presence = $ ( presence ) ,
2015-04-06 20:55:38 +02:00
presence _type = presence . getAttribute ( 'type' ) ;
if ( presence _type === 'error' ) { return true ; }
var jid = presence . getAttribute ( 'from' ) ,
2013-06-02 00:21:06 +02:00
bare _jid = Strophe . getBareJidFromJid ( jid ) ,
resource = Strophe . getResourceFromJid ( jid ) ,
2015-04-07 18:23:45 +02:00
chat _status = $presence . find ( 'show' ) . text ( ) || 'online' ,
2013-06-02 00:21:06 +02:00
status _message = $presence . find ( 'status' ) ,
2015-04-06 20:55:38 +02:00
contact = this . get ( bare _jid ) ;
2013-06-02 00:21:06 +02:00
if ( this . isSelf ( bare _jid ) ) {
2013-08-04 15:39:46 +02:00
if ( ( converse . connection . jid !== jid ) && ( presence _type !== 'unavailable' ) ) {
2015-06-27 21:21:27 +02:00
// Another resource has changed its status, we'll update ours as well.
2013-06-02 00:21:06 +02:00
converse . xmppstatus . save ( { 'status' : chat _status } ) ;
2015-09-09 13:19:26 +02:00
if ( status _message . length ) { converse . xmppstatus . save ( { 'status_message' : status _message . text ( ) } ) ; }
2013-06-02 00:21:06 +02:00
}
2015-04-07 18:23:45 +02:00
return ;
2013-06-02 00:21:06 +02:00
} else if ( ( $presence . find ( 'x' ) . attr ( 'xmlns' ) || '' ) . indexOf ( Strophe . NS . MUC ) === 0 ) {
2015-04-07 18:23:45 +02:00
return ; // Ignore MUC
2013-06-02 00:21:06 +02:00
}
2015-10-25 18:49:35 +01:00
if ( contact && ( status _message . text ( ) !== contact . get ( 'status' ) ) ) {
2014-08-03 19:42:23 +02:00
contact . save ( { 'status' : status _message . text ( ) } ) ;
2013-05-30 21:24:00 +02:00
}
2015-04-07 20:04:27 +02:00
if ( presence _type === 'subscribed' && contact ) {
2015-04-10 13:01:31 +02:00
contact . ackSubscribe ( ) ;
} else if ( presence _type === 'unsubscribed' && contact ) {
contact . ackUnsubscribe ( ) ;
2015-04-07 20:04:27 +02:00
} else if ( presence _type === 'unsubscribe' ) {
2015-04-07 18:23:45 +02:00
return ;
2013-06-02 00:21:06 +02:00
} else if ( presence _type === 'subscribe' ) {
2015-04-06 13:41:48 +02:00
this . handleIncomingSubscription ( jid ) ;
2015-04-07 18:23:45 +02:00
} else if ( presence _type === 'unavailable' && contact ) {
2015-04-13 17:02:14 +02:00
// Only set the user to offline if there aren't any
// other resources still available.
if ( contact . removeResource ( resource ) === 0 ) {
contact . save ( { 'chat_status' : "offline" } ) ;
}
2015-04-07 18:23:45 +02:00
} else if ( contact ) { // presence_type is undefined
2013-06-02 00:21:06 +02:00
this . addResource ( bare _jid , resource ) ;
2014-08-03 19:42:23 +02:00
contact . save ( { 'chat_status' : chat _status } ) ;
2013-05-31 16:55:58 +02:00
}
2012-07-21 19:09:30 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2012-07-08 22:18:49 +02:00
2014-08-02 14:25:24 +02:00
this . RosterGroup = Backbone . Model . extend ( {
initialize : function ( attributes , options ) {
this . set ( _ . extend ( {
description : DESC _GROUP _TOGGLE ,
2014-08-02 15:05:27 +02:00
state : OPENED
2014-08-03 23:07:48 +02:00
} , attributes ) ) ;
2014-08-02 15:05:27 +02:00
// Collection of contacts belonging to this group.
this . contacts = new converse . RosterContacts ( ) ;
2014-08-02 14:25:24 +02:00
}
} ) ;
2014-08-02 15:05:27 +02:00
this . RosterGroupView = Backbone . Overview . extend ( {
2014-08-03 23:55:20 +02:00
tagName : 'dt' ,
className : 'roster-group' ,
2014-07-20 19:11:34 +02:00
events : {
2014-08-02 15:05:27 +02:00
"click a.group-toggle" : "toggle"
} ,
initialize : function ( ) {
this . model . contacts . on ( "add" , this . addContact , this ) ;
2014-09-15 22:48:04 +02:00
this . model . contacts . on ( "change:subscription" , this . onContactSubscriptionChange , this ) ;
this . model . contacts . on ( "change:requesting" , this . onContactRequestChange , this ) ;
2014-08-02 15:05:27 +02:00
this . model . contacts . on ( "change:chat_status" , function ( contact ) {
2014-08-07 21:27:42 +02:00
// This might be optimized by instead of first sorting,
// finding the correct position in positionContact
2014-08-02 15:05:27 +02:00
this . model . contacts . sort ( ) ;
2014-08-03 23:55:20 +02:00
this . positionContact ( contact ) . render ( ) ;
2014-08-02 15:05:27 +02:00
} , this ) ;
this . model . contacts . on ( "destroy" , this . onRemove , this ) ;
this . model . contacts . on ( "remove" , this . onRemove , this ) ;
2014-08-03 23:02:25 +02:00
converse . roster . on ( 'change:groups' , this . onContactGroupChange , this ) ;
2014-07-20 19:11:34 +02:00
} ,
2013-03-24 10:18:26 +01:00
2014-08-02 14:25:24 +02:00
render : function ( ) {
2014-08-04 18:17:34 +02:00
this . $el . attr ( 'data-group' , this . model . get ( 'name' ) ) ;
2014-08-03 23:55:20 +02:00
this . $el . html (
2014-08-02 14:25:24 +02:00
$ ( converse . templates . group _header ( {
label _group : this . model . get ( 'name' ) ,
desc _group _toggle : this . model . get ( 'description' ) ,
2014-08-02 15:05:27 +02:00
toggle _state : this . model . get ( 'state' )
2014-08-02 14:25:24 +02:00
} ) )
) ;
return this ;
} ,
2014-09-15 20:04:36 +02:00
addContact : function ( contact ) {
var view = new converse . RosterContactView ( { model : contact } ) ;
this . add ( contact . get ( 'id' ) , view ) ;
view = this . positionContact ( contact ) . render ( ) ;
2014-10-26 23:10:43 +01:00
if ( contact . showInRoster ( ) ) {
if ( this . model . get ( 'state' ) === CLOSED ) {
if ( view . $el [ 0 ] . style . display !== "none" ) { view . $el . hide ( ) ; }
2015-04-17 17:02:16 +02:00
if ( ! this . $el . is ( ':visible' ) ) { this . $el . show ( ) ; }
2014-10-26 23:10:43 +01:00
} else {
if ( this . $el [ 0 ] . style . display !== "block" ) { this . show ( ) ; }
}
2014-09-15 20:04:36 +02:00
}
} ,
2014-08-02 15:05:27 +02:00
positionContact : function ( contact ) {
/ * P l a c e t h e c o n t a c t ' s D O M e l e m e n t i n t h e c o r r e c t a l p h a b e t i c a l
* position amongst the other contacts in this group .
* /
var view = this . get ( contact . get ( 'id' ) ) ;
var index = this . model . contacts . indexOf ( contact ) ;
2014-08-11 22:16:36 +02:00
view . $el . detach ( ) ;
2014-08-03 23:07:48 +02:00
if ( index === 0 ) {
2014-08-03 23:55:20 +02:00
this . $el . after ( view . $el ) ;
2015-10-25 18:49:35 +01:00
} else if ( index === ( this . model . contacts . length - 1 ) ) {
2014-08-02 15:05:27 +02:00
this . $el . nextUntil ( 'dt' ) . last ( ) . after ( view . $el ) ;
} else {
this . $el . nextUntil ( 'dt' ) . eq ( index ) . before ( view . $el ) ;
}
return view ;
} ,
2014-08-31 19:25:54 +02:00
show : function ( ) {
2015-04-17 16:24:02 +02:00
this . $el . show ( ) ;
_ . each ( this . getAll ( ) , function ( contactView ) {
if ( contactView . model . showInRoster ( ) ) {
contactView . $el . show ( ) ;
}
} ) ;
2014-08-31 19:25:54 +02:00
} ,
2014-08-11 21:47:51 +02:00
hide : function ( ) {
this . $el . nextUntil ( 'dt' ) . addBack ( ) . hide ( ) ;
} ,
2014-08-08 19:08:47 +02:00
filter : function ( q ) {
2014-08-08 21:41:47 +02:00
/ * F i l t e r t h e g r o u p ' s c o n t a c t s b a s e d o n t h e q u e r y " q " .
* The query is matched against the contact ' s full name .
* If all contacts are filtered out ( i . e . hidden ) , then the
* group must be filtered out as well .
* /
2015-08-24 17:01:55 +02:00
var matches ;
2014-08-08 19:08:47 +02:00
if ( q . length === 0 ) {
if ( this . model . get ( 'state' ) === OPENED ) {
2015-06-27 06:36:25 +02:00
this . model . contacts . each ( function ( item ) {
2014-10-26 23:10:43 +01:00
if ( item . showInRoster ( ) ) {
2014-08-08 19:08:47 +02:00
this . get ( item . get ( 'id' ) ) . $el . show ( ) ;
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-08-08 19:08:47 +02:00
}
2014-11-15 14:15:07 +01:00
this . showIfNecessary ( ) ;
2014-08-08 19:08:47 +02:00
} else {
q = q . toLowerCase ( ) ;
2014-08-30 13:26:10 +02:00
matches = this . model . contacts . filter ( contains . not ( 'fullname' , q ) ) ;
2014-08-08 19:08:47 +02:00
if ( matches . length === this . model . contacts . length ) { // hide the whole group
2014-08-11 21:47:51 +02:00
this . hide ( ) ;
2014-08-08 19:08:47 +02:00
} else {
2015-06-27 06:36:25 +02:00
_ . each ( matches , function ( item ) {
2014-08-08 19:08:47 +02:00
this . get ( item . get ( 'id' ) ) . $el . hide ( ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
_ . each ( this . model . contacts . reject ( contains . not ( 'fullname' , q ) ) , function ( item ) {
2014-08-08 21:41:47 +02:00
this . get ( item . get ( 'id' ) ) . $el . show ( ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-11-15 14:15:07 +01:00
this . showIfNecessary ( ) ;
2014-08-08 19:08:47 +02:00
}
}
} ,
2014-11-15 14:15:07 +01:00
showIfNecessary : function ( ) {
if ( ! this . $el . is ( ':visible' ) && this . model . contacts . length > 0 ) {
2014-08-08 19:08:47 +02:00
this . $el . show ( ) ;
}
} ,
2014-08-02 15:05:27 +02:00
toggle : function ( ev ) {
2014-08-02 14:25:24 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var $el = $ ( ev . target ) ;
if ( $el . hasClass ( "icon-opened" ) ) {
2014-08-08 21:41:47 +02:00
this . $el . nextUntil ( 'dt' ) . slideUp ( ) ;
2014-08-04 19:12:03 +02:00
this . model . save ( { state : CLOSED } ) ;
2014-08-02 14:25:24 +02:00
$el . removeClass ( "icon-opened" ) . addClass ( "icon-closed" ) ;
} else {
$el . removeClass ( "icon-closed" ) . addClass ( "icon-opened" ) ;
2014-08-04 19:12:03 +02:00
this . model . save ( { state : OPENED } ) ;
2014-08-11 21:47:51 +02:00
this . filter (
converse . rosterview . $ ( '.roster-filter' ) . val ( ) ,
converse . rosterview . $ ( '.filter-type' ) . val ( )
) ;
2014-08-02 14:25:24 +02:00
}
2014-08-02 15:05:27 +02:00
} ,
onContactGroupChange : function ( contact ) {
var in _this _group = _ . contains ( contact . get ( 'groups' ) , this . model . get ( 'name' ) ) ;
var cid = contact . get ( 'id' ) ;
var in _this _overview = ! this . get ( cid ) ;
if ( in _this _group && ! in _this _overview ) {
2014-09-15 22:48:04 +02:00
this . model . contacts . remove ( cid ) ;
2014-08-02 15:05:27 +02:00
} else if ( ! in _this _group && in _this _overview ) {
2014-09-15 22:48:04 +02:00
this . addContact ( contact ) ;
}
} ,
onContactSubscriptionChange : function ( contact ) {
if ( ( this . model . get ( 'name' ) === HEADER _PENDING _CONTACTS ) && contact . get ( 'subscription' ) !== 'from' ) {
this . model . contacts . remove ( contact . get ( 'id' ) ) ;
}
} ,
onContactRequestChange : function ( contact ) {
if ( ( this . model . get ( 'name' ) === HEADER _REQUESTING _CONTACTS ) && ! contact . get ( 'requesting' ) ) {
this . model . contacts . remove ( contact . get ( 'id' ) ) ;
2014-08-02 15:05:27 +02:00
}
} ,
onRemove : function ( contact ) {
2014-09-15 22:48:04 +02:00
this . remove ( contact . get ( 'id' ) ) ;
2014-08-02 15:05:27 +02:00
if ( this . model . contacts . length === 0 ) {
this . $el . hide ( ) ;
}
2014-08-02 14:25:24 +02:00
}
} ) ;
this . RosterGroups = Backbone . Collection . extend ( {
model : converse . RosterGroup ,
comparator : function ( a , b ) {
/ * G r o u p s a r e s o r t e d a l p h a b e t i c a l l y , i g n o r i n g c a s e .
* However , Ungrouped , Requesting Contacts and Pending Contacts
* appear last and in that order . * /
a = a . get ( 'name' ) ;
b = b . get ( 'name' ) ;
var special _groups = _ . keys ( HEADER _WEIGHTS ) ;
var a _is _special = _ . contains ( special _groups , a ) ;
var b _is _special = _ . contains ( special _groups , b ) ;
if ( ! a _is _special && ! b _is _special ) {
return a . toLowerCase ( ) < b . toLowerCase ( ) ? - 1 : ( a . toLowerCase ( ) > b . toLowerCase ( ) ? 1 : 0 ) ;
} else if ( a _is _special && b _is _special ) {
return HEADER _WEIGHTS [ a ] < HEADER _WEIGHTS [ b ] ? - 1 : ( HEADER _WEIGHTS [ a ] > HEADER _WEIGHTS [ b ] ? 1 : 0 ) ;
} else if ( ! a _is _special && b _is _special ) {
return ( b === HEADER _CURRENT _CONTACTS ) ? 1 : - 1 ;
} else if ( a _is _special && ! b _is _special ) {
return ( a === HEADER _CURRENT _CONTACTS ) ? - 1 : 1 ;
}
}
} ) ;
2014-08-02 15:05:27 +02:00
this . RosterView = Backbone . Overview . extend ( {
2014-08-07 21:27:42 +02:00
tagName : 'div' ,
2014-08-02 14:25:24 +02:00
id : 'converse-roster' ,
2014-08-08 19:08:47 +02:00
events : {
2014-08-08 21:41:47 +02:00
"keydown .roster-filter" : "liveFilter" ,
"click .onX" : "clearFilter" ,
2014-08-11 21:47:51 +02:00
"mousemove .x" : "togglePointer" ,
"change .filter-type" : "changeFilterType"
2014-08-08 19:08:47 +02:00
} ,
2014-08-02 14:25:24 +02:00
2013-06-02 00:21:06 +02:00
initialize : function ( ) {
2015-04-08 19:51:33 +02:00
this . roster _handler _ref = this . registerRosterHandler ( ) ;
this . rosterx _handler _ref = this . registerRosterXHandler ( ) ;
this . presence _ref = this . registerPresenceHandler ( ) ;
2014-08-03 23:27:10 +02:00
converse . roster . on ( "add" , this . onContactAdd , this ) ;
converse . roster . on ( 'change' , this . onContactChange , this ) ;
2014-08-03 23:02:25 +02:00
converse . roster . on ( "destroy" , this . update , this ) ;
2014-08-03 23:27:10 +02:00
converse . roster . on ( "remove" , this . update , this ) ;
this . model . on ( "add" , this . onGroupAdd , this ) ;
2014-07-31 21:50:34 +02:00
this . model . on ( "reset" , this . reset , this ) ;
2014-10-26 20:20:05 +01:00
this . $roster = $ ( '<dl class="roster-contacts" style="display: none;"></dl>' ) ;
2014-09-15 20:04:36 +02:00
} ,
2015-04-08 19:51:33 +02:00
unregisterHandlers : function ( ) {
converse . connection . deleteHandler ( this . roster _handler _ref ) ;
delete this . roster _handler _ref ;
converse . connection . deleteHandler ( this . rosterx _handler _ref ) ;
delete this . rosterx _handler _ref ;
converse . connection . deleteHandler ( this . presence _ref ) ;
delete this . presence _ref ;
} ,
2014-10-25 09:57:05 +02:00
update : _ . debounce ( function ( ) {
2014-09-15 20:04:36 +02:00
var $count = $ ( '#online-count' ) ;
$count . text ( '(' + converse . roster . getNumOnlineContacts ( ) + ')' ) ;
if ( ! $count . is ( ':visible' ) ) {
$count . show ( ) ;
}
2014-10-25 12:33:24 +02:00
if ( this . $roster . parent ( ) . length === 0 ) {
2014-10-26 20:20:05 +01:00
this . $el . append ( this . $roster . show ( ) ) ;
2014-10-25 12:33:24 +02:00
}
2014-09-15 20:04:36 +02:00
return this . showHideFilter ( ) ;
2014-10-27 23:06:11 +01:00
} , converse . animate ? 100 : 0 ) ,
2014-07-19 19:14:29 +02:00
2014-07-20 22:44:50 +02:00
render : function ( ) {
2014-08-07 21:27:42 +02:00
this . $el . html ( converse . templates . roster ( {
2014-08-11 21:47:51 +02:00
placeholder : _ _ ( 'Type to filter' ) ,
label _contacts : LABEL _CONTACTS ,
label _groups : LABEL _GROUPS
2014-08-07 21:27:42 +02:00
} ) ) ;
2015-04-08 13:12:29 +02:00
if ( ! converse . allow _contact _requests ) {
// XXX: if we ever support live editing of config then
// we'll need to be able to remove this class on the fly.
this . $el . addClass ( 'no-contact-requests' ) ;
}
2014-08-01 20:09:32 +02:00
return this ;
2013-06-02 00:21:06 +02:00
} ,
2012-07-15 21:03:34 +02:00
2014-09-15 20:04:36 +02:00
fetch : function ( ) {
this . model . fetch ( {
2014-10-27 21:53:05 +01:00
silent : true , // We use the success handler to handle groups that were added,
// we need to first have all groups before positionFetchedGroups
// will work properly.
2015-06-27 06:36:25 +02:00
success : function ( collection , resp , options ) {
2014-10-27 21:53:05 +01:00
if ( collection . length !== 0 ) {
this . positionFetchedGroups ( collection , resp , options ) ;
}
converse . roster . fetch ( {
add : true ,
success : function ( collection ) {
if ( collection . length > 0 ) {
converse . initial _presence _sent = 1 ;
} else {
2015-04-06 20:55:38 +02:00
// We don't have any roster contacts stored
// in sessionStorage, so lets fetch the
// roster from the XMPP server.
converse . roster . fetchFromServer ( ) ;
2014-10-27 21:53:05 +01:00
}
}
} ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this )
2014-09-15 20:04:36 +02:00
} ) ;
return this ;
} ,
2014-08-11 21:47:51 +02:00
changeFilterType : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
this . clearFilter ( ) ;
this . filter (
this . $ ( '.roster-filter' ) . val ( ) ,
ev . target . value
) ;
} ,
2014-08-08 21:41:47 +02:00
tog : function ( v ) {
return v ? 'addClass' : 'removeClass' ;
} ,
togglePointer : function ( ev ) {
2014-08-08 19:08:47 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-08-08 21:41:47 +02:00
var el = ev . target ;
$ ( el ) [ this . tog ( el . offsetWidth - 18 < ev . clientX - el . getBoundingClientRect ( ) . left ) ] ( 'onX' ) ;
} ,
2014-08-11 21:47:51 +02:00
filter : function ( query , type ) {
query = query . toLowerCase ( ) ;
if ( type === 'groups' ) {
2014-08-31 19:25:54 +02:00
_ . each ( this . getAll ( ) , function ( view , idx ) {
if ( view . model . get ( 'name' ) . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) === - 1 ) {
view . hide ( ) ;
} else if ( view . model . contacts . length > 0 ) {
view . show ( ) ;
}
2014-08-11 21:47:51 +02:00
} ) ;
} else {
_ . each ( this . getAll ( ) , function ( view ) {
view . filter ( query , type ) ;
} ) ;
}
2014-08-08 21:41:47 +02:00
} ,
liveFilter : _ . debounce ( function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-11-15 14:02:13 +01:00
var $filter = this . $ ( '.roster-filter' ) ;
var q = $filter . val ( ) ;
2014-08-11 21:47:51 +02:00
var t = this . $ ( '.filter-type' ) . val ( ) ;
2014-11-15 14:02:13 +01:00
$filter [ this . tog ( q ) ] ( 'x' ) ;
2014-08-11 21:47:51 +02:00
this . filter ( q , t ) ;
2014-08-18 20:37:38 +02:00
} , 300 ) ,
2014-08-08 19:08:47 +02:00
2014-08-08 21:41:47 +02:00
clearFilter : function ( ev ) {
2014-08-11 21:47:51 +02:00
if ( ev && ev . preventDefault ) {
ev . preventDefault ( ) ;
$ ( ev . target ) . removeClass ( 'x onX' ) . val ( '' ) ;
}
2014-08-08 21:41:47 +02:00
this . filter ( '' ) ;
} ,
2014-08-07 22:18:44 +02:00
showHideFilter : function ( ) {
2014-09-20 15:07:55 +02:00
if ( ! this . $el . is ( ':visible' ) ) {
return ;
}
2014-08-11 21:47:51 +02:00
var $filter = this . $ ( '.roster-filter' ) ;
2014-08-11 22:26:10 +02:00
var $type = this . $ ( '.filter-type' ) ;
2014-08-11 21:47:51 +02:00
var visible = $filter . is ( ':visible' ) ;
if ( visible && $filter . val ( ) . length > 0 ) {
2014-08-08 21:41:47 +02:00
// Don't hide if user is currently filtering.
return ;
}
2014-10-25 12:33:24 +02:00
if ( this . $roster . hasScrollBar ( ) ) {
2014-08-08 21:41:47 +02:00
if ( ! visible ) {
$filter . show ( ) ;
2014-08-11 22:26:10 +02:00
$type . show ( ) ;
2014-08-08 21:41:47 +02:00
}
2014-08-07 22:18:44 +02:00
} else {
$filter . hide ( ) ;
2014-08-11 22:26:10 +02:00
$type . hide ( ) ;
2014-08-07 22:18:44 +02:00
}
return this ;
} ,
2014-07-31 21:50:34 +02:00
reset : function ( ) {
2014-08-03 23:02:25 +02:00
converse . roster . reset ( ) ;
2014-08-02 15:05:27 +02:00
this . removeAll ( ) ;
2014-10-27 23:06:11 +01:00
this . $roster = $ ( '<dl class="roster-contacts" style="display: none;"></dl>' ) ;
2014-08-01 20:09:32 +02:00
this . render ( ) . update ( ) ;
2014-07-31 21:50:34 +02:00
return this ;
} ,
2014-08-02 15:05:27 +02:00
registerRosterHandler : function ( ) {
2015-04-06 20:55:38 +02:00
converse . connection . addHandler (
converse . roster . onRosterPush . bind ( converse . roster ) ,
Strophe . NS . ROSTER , 'iq' , "set"
2014-10-27 21:35:06 +01:00
) ;
2014-08-02 15:05:27 +02:00
} ,
registerRosterXHandler : function ( ) {
2014-10-27 18:48:54 +01:00
var t = 0 ;
2014-08-02 15:05:27 +02:00
converse . connection . addHandler (
2014-10-27 18:48:54 +01:00
function ( msg ) {
window . setTimeout (
function ( ) {
converse . connection . flush ( ) ;
2015-06-27 06:36:25 +02:00
converse . roster . subscribeToSuggestedItems . bind ( converse . roster ) ( msg ) ;
2014-10-27 18:48:54 +01:00
} ,
t
) ;
t += $ ( msg ) . find ( 'item' ) . length * 250 ;
return true ;
} ,
2015-04-15 22:06:23 +02:00
Strophe . NS . ROSTERX , 'message' , null
2015-04-06 20:55:38 +02:00
) ;
2014-08-02 15:05:27 +02:00
} ,
registerPresenceHandler : function ( ) {
converse . connection . addHandler (
2015-06-27 06:36:25 +02:00
function ( presence ) {
2014-08-03 23:02:25 +02:00
converse . roster . presenceHandler ( presence ) ;
2014-08-02 15:05:27 +02:00
return true ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) , null , 'presence' , null ) ;
2014-08-02 15:05:27 +02:00
} ,
2014-08-03 23:27:10 +02:00
onGroupAdd : function ( group ) {
var view = new converse . RosterGroupView ( { model : group } ) ;
2014-08-03 23:55:20 +02:00
this . add ( group . get ( 'name' ) , view . render ( ) ) ;
2014-08-03 23:27:10 +02:00
this . positionGroup ( view ) ;
} ,
onContactAdd : function ( contact ) {
2014-08-03 19:42:23 +02:00
this . addRosterContact ( contact ) . update ( ) ;
if ( ! contact . get ( 'vcard_updated' ) ) {
2014-07-25 08:56:54 +02:00
// This will update the vcard, which triggers a change
2014-08-03 19:42:23 +02:00
// request which will rerender the roster contact.
converse . getVCard ( contact . get ( 'jid' ) ) ;
2014-07-25 08:56:54 +02:00
}
} ,
2014-08-03 23:27:10 +02:00
onContactChange : function ( contact ) {
2014-08-03 19:42:23 +02:00
this . updateChatBox ( contact ) . update ( ) ;
2014-09-15 22:48:04 +02:00
if ( _ . has ( contact . changed , 'subscription' ) ) {
2015-10-25 18:49:35 +01:00
if ( contact . changed . subscription === 'from' ) {
2014-09-15 22:48:04 +02:00
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
2015-04-08 19:51:33 +02:00
} else if ( _ . contains ( [ 'both' , 'to' ] , contact . get ( 'subscription' ) ) ) {
2014-09-15 22:48:04 +02:00
this . addExistingContact ( contact ) ;
}
}
2015-10-25 18:49:35 +01:00
if ( _ . has ( contact . changed , 'ask' ) && contact . changed . ask === 'subscribe' ) {
2014-09-15 23:28:07 +02:00
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
}
2015-10-25 18:49:35 +01:00
if ( _ . has ( contact . changed , 'subscription' ) && contact . changed . requesting === 'true' ) {
2014-09-15 23:28:07 +02:00
this . addContactToGroup ( contact , HEADER _REQUESTING _CONTACTS ) ;
2014-09-15 22:48:04 +02:00
}
2014-11-15 14:02:13 +01:00
this . liveFilter ( ) ;
2014-07-25 08:56:54 +02:00
} ,
2014-09-15 22:48:04 +02:00
updateChatBox : function ( contact ) {
2014-08-03 19:42:23 +02:00
var chatbox = converse . chatboxes . get ( contact . get ( 'jid' ) ) ,
2013-06-02 00:21:06 +02:00
changes = { } ;
2013-10-07 09:08:11 +02:00
if ( ! chatbox ) {
return this ;
}
2014-08-03 19:42:23 +02:00
if ( _ . has ( contact . changed , 'chat_status' ) ) {
changes . chat _status = contact . get ( 'chat_status' ) ;
2012-09-21 16:04:57 +02:00
}
2014-08-03 19:42:23 +02:00
if ( _ . has ( contact . changed , 'status' ) ) {
changes . status = contact . get ( 'status' ) ;
2012-10-16 22:39:21 +02:00
}
2013-06-02 00:21:06 +02:00
chatbox . save ( changes ) ;
2013-10-07 09:08:11 +02:00
return this ;
} ,
2014-08-04 21:57:21 +02:00
positionFetchedGroups : function ( model , resp , options ) {
/ * I n s t e a d o f t h r o w i n g a n a d d e v e n t f o r e a c h g r o u p
2015-04-06 20:55:38 +02:00
* fetched , we wait until they ' re all fetched and then
* we position them .
* Works around the problem of positionGroup not
* working when all groups besides the one being
* positioned aren ' t already in inserted into the
* roster DOM element .
* /
2014-08-04 21:57:21 +02:00
model . sort ( ) ;
2015-06-27 06:36:25 +02:00
model . each ( function ( group , idx ) {
2014-08-07 21:27:42 +02:00
var view = this . get ( group . get ( 'name' ) ) ;
2014-08-04 21:57:21 +02:00
if ( ! view ) {
view = new converse . RosterGroupView ( { model : group } ) ;
this . add ( group . get ( 'name' ) , view . render ( ) ) ;
}
if ( idx === 0 ) {
2014-10-25 12:33:24 +02:00
this . $roster . append ( view . $el ) ;
2014-08-04 21:57:21 +02:00
} else {
this . appendGroup ( view ) ;
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-08-04 21:57:21 +02:00
} ,
2014-08-02 14:25:24 +02:00
positionGroup : function ( view ) {
/ * P l a c e t h e g r o u p ' s D O M e l e m e n t i n t h e c o r r e c t a l p h a b e t i c a l
* position amongst the other groups in the roster .
* /
2014-10-27 21:35:06 +01:00
var $groups = this . $roster . find ( '.roster-group' ) ,
2014-10-26 23:11:58 +01:00
index = $groups . length ? this . model . indexOf ( view . model ) : 0 ;
2014-08-03 23:07:48 +02:00
if ( index === 0 ) {
2014-10-25 12:33:24 +02:00
this . $roster . prepend ( view . $el ) ;
2015-10-25 18:49:35 +01:00
} else if ( index === ( this . model . length - 1 ) ) {
2014-08-04 21:57:21 +02:00
this . appendGroup ( view ) ;
2014-07-31 18:20:20 +02:00
} else {
2014-10-26 23:11:58 +01:00
$ ( $groups . eq ( index ) ) . before ( view . $el ) ;
2014-07-31 18:20:20 +02:00
}
2014-08-04 21:57:21 +02:00
return this ;
} ,
appendGroup : function ( view ) {
/ * A d d t h e g r o u p a t t h e b o t t o m o f t h e r o s t e r
* /
2014-10-27 21:35:06 +01:00
var $last = this . $roster . find ( '.roster-group' ) . last ( ) ;
2014-08-04 21:57:21 +02:00
var $siblings = $last . siblings ( 'dd' ) ;
if ( $siblings . length > 0 ) {
$siblings . last ( ) . after ( view . $el ) ;
} else {
$last . after ( view . $el ) ;
}
return this ;
2014-08-02 14:25:24 +02:00
} ,
getGroup : function ( name ) {
2014-08-03 23:27:10 +02:00
/ * R e t u r n s t h e g r o u p a s s p e c i f i e d b y n a m e .
* Creates the group if it doesn ' t exist .
2014-08-02 14:25:24 +02:00
* /
2014-08-02 15:05:27 +02:00
var view = this . get ( name ) ;
2014-08-02 14:25:24 +02:00
if ( view ) {
2014-08-03 23:27:10 +02:00
return view . model ;
2014-08-02 14:25:24 +02:00
}
2014-08-03 23:27:10 +02:00
return this . model . create ( { name : name , id : b64 _sha1 ( name ) } ) ;
2014-07-31 18:20:20 +02:00
} ,
2014-08-02 15:05:27 +02:00
addContactToGroup : function ( contact , name ) {
2014-08-03 23:27:10 +02:00
this . getGroup ( name ) . contacts . add ( contact ) ;
2014-08-02 15:05:27 +02:00
} ,
2014-09-15 21:33:44 +02:00
addExistingContact : function ( contact ) {
2014-08-02 14:25:24 +02:00
var groups ;
2014-07-29 19:53:57 +02:00
if ( converse . roster _groups ) {
2014-08-03 19:42:23 +02:00
groups = contact . get ( 'groups' ) ;
2014-08-02 15:05:27 +02:00
if ( groups . length === 0 ) {
groups = [ HEADER _UNGROUPED ] ;
}
2014-07-29 19:53:57 +02:00
} else {
2014-08-02 14:25:24 +02:00
groups = [ HEADER _CURRENT _CONTACTS ] ;
2014-07-29 19:53:57 +02:00
}
2015-06-27 06:36:25 +02:00
_ . each ( groups , _ . bind ( this . addContactToGroup , this , contact ) ) ;
2014-07-29 19:53:57 +02:00
} ,
2014-08-03 19:42:23 +02:00
addRosterContact : function ( contact ) {
if ( contact . get ( 'subscription' ) === 'both' || contact . get ( 'subscription' ) === 'to' ) {
2014-09-15 21:33:44 +02:00
this . addExistingContact ( contact ) ;
2014-08-01 20:09:32 +02:00
} else {
2014-08-03 19:42:23 +02:00
if ( ( contact . get ( 'ask' ) === 'subscribe' ) || ( contact . get ( 'subscription' ) === 'from' ) ) {
2014-08-03 23:07:48 +02:00
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
2014-08-03 19:42:23 +02:00
} else if ( contact . get ( 'requesting' ) === true ) {
2014-08-03 23:07:48 +02:00
this . addContactToGroup ( contact , HEADER _REQUESTING _CONTACTS ) ;
2014-08-01 20:09:32 +02:00
}
2014-07-23 20:10:10 +02:00
}
return this ;
2013-05-31 21:00:54 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
this . XMPPStatus = Backbone . Model . extend ( {
initialize : function ( ) {
this . set ( {
2015-01-23 15:16:16 +01:00
'status' : this . getStatus ( )
2013-06-02 00:21:06 +02:00
} ) ;
2015-06-27 06:36:25 +02:00
this . on ( 'change' , function ( item ) {
2013-08-04 15:39:46 +02:00
if ( this . get ( 'fullname' ) === undefined ) {
converse . getVCard (
null , // No 'to' attr when getting one's own vCard
2015-06-27 06:36:25 +02:00
function ( iq , jid , fullname , image , image _type , url ) {
2013-08-04 15:39:46 +02:00
this . save ( { 'fullname' : fullname } ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this )
2013-08-04 15:39:46 +02:00
) ;
}
2013-12-15 15:58:46 +01:00
if ( _ . has ( item . changed , 'status' ) ) {
2014-07-06 18:31:17 +02:00
converse . emit ( 'statusChanged' , this . get ( 'status' ) ) ;
2013-12-15 15:58:46 +01:00
}
if ( _ . has ( item . changed , 'status_message' ) ) {
2014-07-06 18:31:17 +02:00
converse . emit ( 'statusMessageChanged' , this . get ( 'status_message' ) ) ;
2013-12-15 15:58:46 +01:00
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
} ,
2015-05-10 00:51:36 +02:00
constructPresence : function ( type , status _message ) {
2015-01-23 15:16:16 +01:00
if ( typeof type === 'undefined' ) {
2013-08-04 15:39:46 +02:00
type = this . get ( 'status' ) || 'online' ;
}
2015-01-23 15:16:16 +01:00
if ( typeof status _message === 'undefined' ) {
status _message = this . get ( 'status_message' ) ;
}
var presence ;
2013-06-02 00:21:06 +02:00
// Most of these presence types are actually not explicitly sent,
// but I add all of them here fore reference and future proofing.
if ( ( type === 'unavailable' ) ||
( type === 'probe' ) ||
( type === 'error' ) ||
( type === 'unsubscribe' ) ||
( type === 'unsubscribed' ) ||
( type === 'subscribe' ) ||
( type === 'subscribed' ) ) {
2014-11-15 16:09:36 +01:00
presence = $pres ( { 'type' : type } ) ;
} else if ( type === 'offline' ) {
presence = $pres ( { 'type' : 'unavailable' } ) ;
if ( status _message ) {
presence . c ( 'show' ) . t ( type ) ;
}
2012-07-28 16:29:54 +02:00
} else {
2013-06-02 00:21:06 +02:00
if ( type === 'online' ) {
presence = $pres ( ) ;
2012-10-16 23:10:42 +02:00
} else {
2013-06-02 00:21:06 +02:00
presence = $pres ( ) . c ( 'show' ) . t ( type ) . up ( ) ;
2012-10-16 23:10:42 +02:00
}
2013-06-02 00:21:06 +02:00
if ( status _message ) {
presence . c ( 'status' ) . t ( status _message ) ;
2012-09-21 16:04:57 +02:00
}
2012-07-08 22:18:49 +02:00
}
2015-05-10 00:51:36 +02:00
return presence ;
} ,
sendPresence : function ( type , status _message ) {
converse . connection . send ( this . constructPresence ( type , status _message ) ) ;
2013-06-02 00:21:06 +02:00
} ,
setStatus : function ( value ) {
this . sendPresence ( value ) ;
this . save ( { 'status' : value } ) ;
} ,
2015-05-27 20:58:02 +02:00
getStatus : function ( ) {
2015-01-23 15:16:16 +01:00
return this . get ( 'status' ) || 'online' ;
} ,
2013-06-02 00:21:06 +02:00
setStatusMessage : function ( status _message ) {
2015-01-23 15:16:16 +01:00
this . sendPresence ( this . getStatus ( ) , status _message ) ;
2015-02-27 18:32:23 +01:00
var prev _status = this . get ( 'status_message' ) ;
2013-06-02 00:21:06 +02:00
this . save ( { 'status_message' : status _message } ) ;
2013-08-27 23:52:44 +02:00
if ( this . xhr _custom _status ) {
$ . ajax ( {
2013-10-20 18:34:20 +02:00
url : this . xhr _custom _status _url ,
2013-08-27 23:52:44 +02:00
type : 'POST' ,
data : { 'msg' : status _message }
} ) ;
}
2015-02-27 18:32:23 +01:00
if ( prev _status === status _message ) {
this . trigger ( "update-status-ui" , this ) ;
}
2013-03-12 09:56:50 +01:00
}
2013-06-02 00:21:06 +02:00
} ) ;
this . XMPPStatusView = Backbone . View . extend ( {
el : "span#xmpp-status-holder" ,
events : {
"click a.choose-xmpp-status" : "toggleOptions" ,
"click #fancy-xmpp-status-select a.change-xmpp-status-message" : "renderStatusChangeForm" ,
"submit #set-custom-xmpp-status" : "setStatusMessage" ,
"click .dropdown dd ul li a" : "setStatus"
} ,
2014-09-07 13:18:36 +02:00
initialize : function ( ) {
2015-02-27 18:32:23 +01:00
this . model . on ( "change:status" , this . updateStatusUI , this ) ;
this . model . on ( "change:status_message" , this . updateStatusUI , this ) ;
this . model . on ( "update-status-ui" , this . updateStatusUI , this ) ;
2014-09-07 13:18:36 +02:00
} ,
render : function ( ) {
// Replace the default dropdown with something nicer
var $select = this . $el . find ( 'select#select-xmpp-status' ) ,
chat _status = this . model . get ( 'status' ) || 'offline' ,
options = $ ( 'option' , $select ) ,
$options _target ,
2015-10-25 18:49:35 +01:00
options _list = [ ] ;
2014-09-07 13:18:36 +02:00
this . $el . html ( converse . templates . choose _status ( ) ) ;
this . $el . find ( '#fancy-xmpp-status-select' )
. html ( converse . templates . chat _status ( {
'status_message' : this . model . get ( 'status_message' ) || _ _ ( "I am %1$s" , this . getPrettyStatus ( chat _status ) ) ,
'chat_status' : chat _status ,
'desc_custom_status' : _ _ ( 'Click here to write a custom status message' ) ,
'desc_change_status' : _ _ ( 'Click to change your chat status' )
} ) ) ;
// iterate through all the <option> elements and add option values
2015-05-27 20:58:02 +02:00
options . each ( function ( ) {
2014-09-07 13:18:36 +02:00
options _list . push ( converse . templates . status _option ( {
'value' : $ ( this ) . val ( ) ,
'text' : this . text
} ) ) ;
} ) ;
$options _target = this . $el . find ( "#target dd ul" ) . hide ( ) ;
$options _target . append ( options _list . join ( '' ) ) ;
$select . remove ( ) ;
return this ;
} ,
2013-06-02 00:21:06 +02:00
toggleOptions : function ( ev ) {
ev . preventDefault ( ) ;
$ ( ev . target ) . parent ( ) . parent ( ) . siblings ( 'dd' ) . find ( 'ul' ) . toggle ( 'fast' ) ;
} ,
renderStatusChangeForm : function ( ev ) {
ev . preventDefault ( ) ;
var status _message = this . model . get ( 'status' ) || 'offline' ;
2014-01-19 06:10:26 +01:00
var input = converse . templates . change _status _message ( {
'status_message' : status _message ,
'label_custom_status' : _ _ ( 'Custom status' ) ,
'label_save' : _ _ ( 'Save' )
} ) ;
2015-11-03 18:14:13 +01:00
var $xmppstatus = this . $el . find ( '.xmpp-status' ) ;
$xmppstatus . parent ( ) . addClass ( 'no-border' ) ;
$xmppstatus . replaceWith ( input ) ;
2013-06-02 00:21:06 +02:00
this . $el . find ( '.custom-xmpp-status' ) . focus ( ) . focus ( ) ;
} ,
setStatusMessage : function ( ev ) {
ev . preventDefault ( ) ;
2015-02-27 18:32:23 +01:00
this . model . setStatusMessage ( $ ( ev . target ) . find ( 'input' ) . val ( ) ) ;
2013-06-02 00:21:06 +02:00
} ,
setStatus : function ( ev ) {
ev . preventDefault ( ) ;
var $el = $ ( ev . target ) ,
value = $el . attr ( 'data-value' ) ;
2014-09-07 13:18:36 +02:00
if ( value === 'logout' ) {
this . $el . find ( ".dropdown dd ul" ) . hide ( ) ;
converse . logOut ( ) ;
} else {
this . model . setStatus ( value ) ;
this . $el . find ( ".dropdown dd ul" ) . hide ( ) ;
}
2013-06-02 00:21:06 +02:00
} ,
2013-03-12 09:56:50 +01:00
2013-06-02 00:21:06 +02:00
getPrettyStatus : function ( stat ) {
if ( stat === 'chat' ) {
2015-06-07 18:29:36 +02:00
return _ _ ( 'online' ) ;
2013-06-02 00:21:06 +02:00
} else if ( stat === 'dnd' ) {
2015-06-07 18:29:36 +02:00
return _ _ ( 'busy' ) ;
2013-06-02 00:21:06 +02:00
} else if ( stat === 'xa' ) {
2015-06-07 18:29:36 +02:00
return _ _ ( 'away for long' ) ;
2013-06-02 00:21:06 +02:00
} else if ( stat === 'away' ) {
2015-06-07 18:29:36 +02:00
return _ _ ( 'away' ) ;
2015-05-26 18:36:17 +02:00
} else if ( stat === 'offline' ) {
2015-06-07 18:29:36 +02:00
return _ _ ( 'offline' ) ;
2013-06-02 00:21:06 +02:00
} else {
2015-06-07 18:29:36 +02:00
return _ _ ( stat ) || _ _ ( 'online' ) ;
2013-06-02 00:21:06 +02:00
}
} ,
2012-12-09 20:47:12 +01:00
2013-06-02 00:21:06 +02:00
updateStatusUI : function ( model ) {
var stat = model . get ( 'status' ) ;
// # For translators: the %1$s part gets replaced with the status
// # Example, I am online
var status _message = model . get ( 'status_message' ) || _ _ ( "I am %1$s" , this . getPrettyStatus ( stat ) ) ;
2015-11-03 18:14:13 +01:00
this . $el . find ( '#fancy-xmpp-status-select' ) . removeClass ( 'no-border' ) . html (
2014-01-19 06:10:26 +01:00
converse . templates . chat _status ( {
2013-06-02 00:21:06 +02:00
'chat_status' : stat ,
2014-01-19 06:10:26 +01:00
'status_message' : status _message ,
'desc_custom_status' : _ _ ( 'Click here to write a custom status message' ) ,
'desc_change_status' : _ _ ( 'Click to change your chat status' )
2013-06-02 00:21:06 +02:00
} ) ) ;
2013-03-25 12:08:27 +01:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2012-12-09 20:47:12 +01:00
2015-06-19 18:01:42 +02:00
this . Session = Backbone . Model ; // General session settings to be saved to sessionStorage.
2014-06-08 21:43:00 +02:00
this . Feature = Backbone . Model ;
2013-06-02 00:21:06 +02:00
this . Features = Backbone . Collection . extend ( {
/ * S e r v i c e D i s c o v e r y
* -- -- -- -- -- -- -- -- -
* This collection stores Feature Models , representing features
* provided by available XMPP entities ( e . g . servers )
* See XEP - 0030 for more details : http : //xmpp.org/extensions/xep-0030.html
* All features are shown here : http : //xmpp.org/registrar/disco-features.html
* /
model : converse . Feature ,
initialize : function ( ) {
2014-08-31 14:14:50 +02:00
this . addClientIdentities ( ) . addClientFeatures ( ) ;
2014-06-30 18:53:58 +02:00
this . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
2014-04-19 05:12:24 +02:00
b64 _sha1 ( 'converse.features' + converse . bare _jid ) ) ;
2015-07-10 15:07:53 +02:00
this . on ( 'add' , this . onFeatureAdded , this ) ;
2014-06-30 18:53:58 +02:00
if ( this . browserStorage . records . length === 0 ) {
// browserStorage is empty, so we've likely never queried this
2013-06-02 00:21:06 +02:00
// domain for features yet
2015-06-27 06:36:25 +02:00
converse . connection . disco . info ( converse . domain , null , this . onInfo . bind ( this ) ) ;
converse . connection . disco . items ( converse . domain , null , this . onItems . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
} else {
this . fetch ( { add : true } ) ;
2013-02-20 17:21:07 +01:00
}
2013-06-02 00:21:06 +02:00
} ,
2015-07-10 15:07:53 +02:00
onFeatureAdded : function ( feature ) {
2015-07-17 16:42:58 +02:00
var prefs = feature . get ( 'preferences' ) || { } ;
2015-07-17 20:33:31 +02:00
converse . emit ( 'serviceDiscovered' , feature ) ;
2015-10-25 18:49:35 +01:00
if ( feature . get ( 'var' ) === Strophe . NS . MAM && prefs [ 'default' ] !== converse . message _archiving ) {
2015-07-10 15:07:53 +02:00
// Ask the server for archiving preferences
converse . connection . sendIQ (
$iq ( { 'type' : 'get' } ) . c ( 'prefs' , { 'xmlns' : Strophe . NS . MAM } ) ,
_ . bind ( this . onMAMPreferences , this , feature ) ,
_ . bind ( this . onMAMError , this , feature )
) ;
}
} ,
onMAMPreferences : function ( feature , iq ) {
/ * H a n d l e r e t u r n e d I Q s t a n z a c o n t a i n i n g M e s s a g e A r c h i v e
* Management ( XEP - 0313 ) preferences .
*
* XXX : For now we only handle the global default preference .
* The XEP also provides for per - JID preferences , which is
* currently not supported in converse . js .
*
* Per JID preferences will be set in chat boxes , so it ' ll
* probbaly be handled elsewhere in any case .
* /
var $prefs = $ ( iq ) . find ( 'prefs[xmlns="' + Strophe . NS . MAM + '"]' ) ;
var default _pref = $prefs . attr ( 'default' ) ;
var stanza ;
if ( default _pref !== converse . message _archiving ) {
stanza = $iq ( { 'type' : 'set' } ) . c ( 'prefs' , { 'xmlns' : Strophe . NS . MAM , 'default' : converse . message _archiving } ) ;
$prefs . children ( ) . each ( function ( idx , child ) {
stanza . cnode ( child ) . up ( ) ;
} ) ;
converse . connection . sendIQ ( stanza , _ . bind ( function ( feature , iq ) {
// XXX: Strictly speaking, the server should respond with the updated prefs
// (see example 18: https://xmpp.org/extensions/xep-0313.html#config)
// but Prosody doesn't do this, so we don't rely on it.
feature . save ( { 'preferences' : { 'default' : converse . message _archiving } } ) ;
} , this , feature ) ,
_ . bind ( this . onMAMError , this , feature )
) ;
} else {
feature . save ( { 'preferences' : { 'default' : converse . message _archiving } } ) ;
}
} ,
onMAMError : function ( iq ) {
if ( $ ( iq ) . find ( 'feature-not-implemented' ) . length ) {
converse . log ( "Message Archive Management (XEP-0313) not supported by this browser" ) ;
} else {
converse . log ( "An error occured while trying to set archiving preferences." ) ;
converse . log ( iq ) ;
}
} ,
2014-08-31 14:14:50 +02:00
addClientIdentities : function ( ) {
/* See http:/ / xmpp . org / registrar / disco - categories . html
* /
converse . connection . disco . addIdentity ( 'client' , 'web' , 'Converse.js' ) ;
return this ;
} ,
addClientFeatures : function ( ) {
/ * T h e s t r o p h e . d i s c o . j s p l u g i n k e e p s a l i s t o f f e a t u r e s w h i c h
* it will advertise to any # info queries made to it .
*
* See : http : //xmpp.org/extensions/xep-0030.html#info
* /
2015-07-10 15:07:32 +02:00
converse . connection . disco . addFeature ( 'jabber:x:conference' ) ;
converse . connection . disco . addFeature ( Strophe . NS . BOSH ) ;
converse . connection . disco . addFeature ( Strophe . NS . CHATSTATES ) ;
converse . connection . disco . addFeature ( Strophe . NS . DISCO _INFO ) ;
converse . connection . disco . addFeature ( Strophe . NS . MAM ) ;
converse . connection . disco . addFeature ( Strophe . NS . ROSTERX ) ; // Limited support
if ( converse . use _vcards ) {
converse . connection . disco . addFeature ( Strophe . NS . VCARD ) ;
}
if ( converse . allow _muc ) {
converse . connection . disco . addFeature ( Strophe . NS . MUC ) ;
}
if ( converse . message _carbons ) {
converse . connection . disco . addFeature ( Strophe . NS . CARBONS ) ;
}
return this ;
2014-08-31 14:14:50 +02:00
} ,
2013-06-02 00:21:06 +02:00
onItems : function ( stanza ) {
2015-06-27 06:36:25 +02:00
$ ( stanza ) . find ( 'query item' ) . each ( function ( idx , item ) {
2013-06-02 00:21:06 +02:00
converse . connection . disco . info (
$ ( item ) . attr ( 'jid' ) ,
null ,
2015-06-27 06:36:25 +02:00
this . onInfo . bind ( this ) ) ;
2015-07-01 19:39:05 +02:00
} . bind ( this ) ) ;
2013-06-02 00:21:06 +02:00
} ,
onInfo : function ( stanza ) {
var $stanza = $ ( stanza ) ;
if ( ( $stanza . find ( 'identity[category=server][type=im]' ) . length === 0 ) &&
( $stanza . find ( 'identity[category=conference][type=text]' ) . length === 0 ) ) {
// This isn't an IM server component
return ;
2013-03-15 06:22:37 +01:00
}
2015-06-27 06:36:25 +02:00
$stanza . find ( 'feature' ) . each ( function ( idx , feature ) {
2015-05-27 21:53:15 +02:00
var namespace = $ ( feature ) . attr ( 'var' ) ;
this [ namespace ] = true ;
2013-06-02 00:21:06 +02:00
this . create ( {
2015-05-27 21:53:15 +02:00
'var' : namespace ,
2013-06-02 00:21:06 +02:00
'from' : $stanza . attr ( 'from' )
} ) ;
2015-07-01 19:39:05 +02:00
} . bind ( this ) ) ;
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2014-11-16 12:47:30 +01:00
this . RegisterPanel = Backbone . View . extend ( {
2013-06-02 00:21:06 +02:00
tagName : 'div' ,
2014-11-16 12:47:30 +01:00
id : "register" ,
className : 'controlbox-pane' ,
2013-06-02 00:21:06 +02:00
events : {
2014-11-18 11:23:50 +01:00
'submit form#converse-register' : 'onProviderChosen'
2013-06-02 00:21:06 +02:00
} ,
2014-11-16 12:47:30 +01:00
initialize : function ( cfg ) {
2014-11-18 11:23:50 +01:00
this . reset ( ) ;
2014-11-16 12:47:30 +01:00
this . $parent = cfg . $parent ;
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
2014-11-18 11:23:50 +01:00
this . registerHooks ( ) ;
2014-11-16 12:47:30 +01:00
} ,
render : function ( ) {
this . $parent . append ( this . $el . html (
converse . templates . register _panel ( {
2014-11-26 21:26:28 +01:00
'label_domain' : _ _ ( "Your XMPP provider's domain name:" ) ,
2014-12-06 17:29:28 +01:00
'label_register' : _ _ ( 'Fetch registration form' ) ,
'help_providers' : _ _ ( 'Tip: A list of public XMPP providers is available' ) ,
'help_providers_link' : _ _ ( 'here' ) ,
'href_providers' : converse . providers _link ,
'domain_placeholder' : converse . domain _placeholder
2014-11-16 12:47:30 +01:00
} )
) ) ;
this . $tabs . append ( converse . templates . register _tab ( { label _register : _ _ ( 'Register' ) } ) ) ;
return this ;
} ,
2014-11-18 11:23:50 +01:00
registerHooks : function ( ) {
/ * H o o k i n t o S t r o p h e ' s _ c o n n e c t _ c b , s o t h a t w e c a n s e n d a n I Q
* requesting the registration fields .
* /
var conn = converse . connection ;
var connect _cb = conn . _connect _cb . bind ( conn ) ;
2015-06-27 06:36:25 +02:00
conn . _connect _cb = function ( req , callback , raw ) {
2014-11-18 11:23:50 +01:00
if ( ! this . _registering ) {
connect _cb ( req , callback , raw ) ;
} else {
if ( this . getRegistrationFields ( req , callback , raw ) ) {
2014-12-01 20:31:01 +01:00
this . _registering = false ;
2014-11-18 11:23:50 +01:00
}
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ;
2014-11-18 11:23:50 +01:00
} ,
getRegistrationFields : function ( req , _callback , raw ) {
/ * S e n d a n I Q s t a n z a t o t h e X M P P s e r v e r a s k i n g f o r t h e
* registration fields .
* Parameters :
* ( Strophe . Request ) req - The current request
* ( Function ) callback
* /
converse . log ( "sendQueryStanza was called" ) ;
var conn = converse . connection ;
conn . connected = true ;
var body = conn . _proto . _reqToData ( req ) ;
if ( ! body ) { return ; }
if ( conn . _proto . _connect _cb ( body ) === Strophe . Status . CONNFAIL ) {
return false ;
2013-02-24 10:31:47 +01:00
}
2014-11-18 11:23:50 +01:00
var register = body . getElementsByTagName ( "register" ) ;
var mechanisms = body . getElementsByTagName ( "mechanism" ) ;
if ( register . length === 0 && mechanisms . length === 0 ) {
conn . _proto . _no _auth _received ( _callback ) ;
return false ;
}
if ( register . length === 0 ) {
2014-11-19 21:20:36 +01:00
conn . _changeConnectStatus (
2014-12-01 20:31:01 +01:00
Strophe . Status . REGIFAIL ,
_ _ ( 'Sorry, the given provider does not support in band account registration. Please try with a different provider.' )
) ;
2014-11-18 11:23:50 +01:00
return true ;
}
// Send an IQ stanza to get all required data fields
conn . _addSysHandler ( this . onRegistrationFields . bind ( this ) , null , "iq" , null , null ) ;
conn . send ( $iq ( { type : "get" } ) . c ( "query" , { xmlns : Strophe . NS . REGISTER } ) . tree ( ) ) ;
return true ;
} ,
onRegistrationFields : function ( stanza ) {
/ * H a n d l e r f o r R e g i s t r a t i o n F i e l d s R e q u e s t .
*
* Parameters :
* ( XMLElement ) elem - The query stanza .
* /
if ( stanza . getElementsByTagName ( "query" ) . length !== 1 ) {
converse . connection . _changeConnectStatus ( Strophe . Status . REGIFAIL , "unknown" ) ;
return false ;
}
this . setFields ( stanza ) ;
this . renderRegistrationForm ( stanza ) ;
return false ;
} ,
reset : function ( settings ) {
var defaults = {
fields : { } ,
2014-11-23 22:26:38 +01:00
urls : [ ] ,
2014-11-18 11:23:50 +01:00
title : "" ,
instructions : "" ,
registered : false ,
_registering : false ,
2014-11-19 21:20:36 +01:00
domain : null ,
form _type : null
2014-11-18 11:23:50 +01:00
} ;
_ . extend ( this , defaults ) ;
if ( settings ) {
_ . extend ( this , _ . pick ( settings , Object . keys ( defaults ) ) ) ;
}
} ,
onProviderChosen : function ( ev ) {
/ * C a l l b a c k m e t h o d t h a t g e t s c a l l e d w h e n t h e u s e r h a s c h o s e n a n
* XMPP provider .
*
* Parameters :
* ( Submit Event ) ev - Form submission event .
* /
2014-11-16 12:47:30 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var $form = $ ( ev . target ) ,
$domain _input = $form . find ( 'input[name=domain]' ) ,
2015-10-25 18:49:35 +01:00
domain = $domain _input . val ( ) ;
2014-11-18 11:23:50 +01:00
if ( ! domain ) {
$domain _input . addClass ( 'error' ) ;
return ;
}
2014-11-19 21:20:36 +01:00
$form . find ( 'input[type=submit]' ) . hide ( )
2014-12-01 20:31:01 +01:00
. after ( converse . templates . registration _request ( {
cancel : _ _ ( 'Cancel' ) ,
info _message : _ _ ( 'Requesting a registration form from the XMPP server' )
} ) ) ;
2015-06-27 06:36:25 +02:00
$form . find ( 'button.cancel' ) . on ( 'click' , this . cancelRegistration . bind ( this ) ) ;
2014-11-18 11:23:50 +01:00
this . reset ( {
domain : Strophe . getDomainFromJid ( domain ) ,
_registering : true
} ) ;
2015-06-27 06:36:25 +02:00
converse . connection . connect ( this . domain , "" , this . onRegistering . bind ( this ) ) ;
2014-11-17 19:10:19 +01:00
return false ;
2014-11-16 12:47:30 +01:00
} ,
giveFeedback : function ( message , klass ) {
2014-11-19 21:20:36 +01:00
this . $ ( '.reg-feedback' ) . attr ( 'class' , 'reg-feedback' ) . text ( message ) ;
2014-11-16 12:47:30 +01:00
if ( klass ) {
2014-11-19 21:20:36 +01:00
$ ( '.reg-feedback' ) . addClass ( klass ) ;
2014-07-14 23:09:39 +02:00
}
2014-11-16 12:47:30 +01:00
} ,
2014-11-17 13:55:52 +01:00
onRegistering : function ( status , error ) {
2014-11-17 19:10:19 +01:00
var that ;
2015-10-25 18:49:35 +01:00
converse . log ( 'onRegistering' ) ;
2014-11-20 11:51:12 +01:00
if ( _ . contains ( [
Strophe . Status . DISCONNECTED ,
Strophe . Status . CONNFAIL ,
2014-12-01 20:31:01 +01:00
Strophe . Status . REGIFAIL ,
Strophe . Status . NOTACCEPTABLE ,
Strophe . Status . CONFLICT
2014-11-20 11:51:12 +01:00
] , status ) ) {
converse . log ( 'Problem during registration: Strophe.Status is: ' + status ) ;
this . cancelRegistration ( ) ;
if ( error ) {
this . giveFeedback ( error , 'error' ) ;
2014-11-16 12:47:30 +01:00
} else {
2014-11-20 11:51:12 +01:00
this . giveFeedback ( _ _ (
2014-12-01 20:31:01 +01:00
'Something went wrong while establishing a connection with "%1$s". Are you sure it exists?' ,
2014-11-20 11:51:12 +01:00
this . domain
) , 'error' ) ;
2014-11-16 12:47:30 +01:00
}
2015-10-25 18:49:35 +01:00
} else if ( status === Strophe . Status . REGISTERED ) {
2014-11-17 19:10:19 +01:00
converse . log ( "Registered successfully." ) ;
converse . connection . reset ( ) ;
2014-11-18 11:23:50 +01:00
that = this ;
2014-11-17 19:10:19 +01:00
this . $ ( 'form' ) . hide ( function ( ) {
$ ( this ) . replaceWith ( '<span class="spinner centered"/>' ) ;
if ( that . fields . password && that . fields . username ) {
// automatically log the user in
converse . connection . connect (
2015-07-01 19:43:58 +02:00
that . fields . username . toLowerCase ( ) + '@' + that . domain . toLowerCase ( ) ,
2014-11-17 19:10:19 +01:00
that . fields . password ,
2015-06-07 18:22:30 +02:00
converse . onConnectStatusChanged
2014-11-17 19:10:19 +01:00
) ;
converse . chatboxviews . get ( 'controlbox' )
. switchTab ( { target : that . $tabs . find ( '.current' ) } )
. giveFeedback ( _ _ ( 'Now logging you in' ) ) ;
} else {
converse . chatboxviews . get ( 'controlbox' )
. renderLoginPanel ( )
. giveFeedback ( _ _ ( 'Registered successfully' ) ) ;
}
that . reset ( ) ;
} ) ;
2014-11-16 12:47:30 +01:00
}
} ,
2014-11-18 11:23:50 +01:00
renderRegistrationForm : function ( stanza ) {
/ * R e n d e r s t h e r e g i s t r a t i o n f o r m b a s e d o n t h e X F o r m f i e l d s
* received from the XMPP server .
*
* Parameters :
2014-11-20 11:51:12 +01:00
* ( XMLElement ) stanza - The IQ stanza received from the XMPP server .
2014-11-18 11:23:50 +01:00
* /
var $form = this . $ ( 'form' ) ,
$stanza = $ ( stanza ) ,
2015-04-21 17:21:26 +02:00
$fields , $input ;
2014-11-20 11:51:12 +01:00
$form . empty ( ) . append ( converse . templates . registration _form ( {
'domain' : this . domain ,
'title' : this . title ,
'instructions' : this . instructions
} ) ) ;
2015-10-25 18:49:35 +01:00
if ( this . form _type === 'xform' ) {
2014-11-19 21:20:36 +01:00
$fields = $stanza . find ( 'field' ) ;
2015-04-21 17:21:26 +02:00
_ . each ( $fields , function ( field ) {
2014-11-26 21:26:28 +01:00
$form . append ( utils . xForm2webForm . bind ( this , $ ( field ) , $stanza ) ) ;
2015-04-21 17:21:26 +02:00
} . bind ( this ) ) ;
2014-11-19 21:20:36 +01:00
} else {
2014-11-23 22:26:38 +01:00
// Show fields
2015-06-27 06:36:25 +02:00
_ . each ( Object . keys ( this . fields ) , function ( key ) {
2015-10-25 18:49:35 +01:00
if ( key === "username" ) {
2015-04-21 17:21:26 +02:00
$input = templates . form _username ( {
domain : ' @' + this . domain ,
name : key ,
type : "text" ,
label : key ,
value : '' ,
required : 1
} ) ;
} else {
$form . append ( '<label>' + key + '</label>' ) ;
$input = $ ( '<input placeholder="' + key + '" name="' + key + '"></input>' ) ;
if ( key === 'password' || key === 'email' ) {
$input . attr ( 'type' , key ) ;
}
2014-11-23 22:26:38 +01:00
}
$form . append ( $input ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-11-23 22:26:38 +01:00
// Show urls
2015-06-27 06:36:25 +02:00
_ . each ( this . urls , function ( url ) {
2014-11-23 22:26:38 +01:00
$form . append ( $ ( '<a target="blank"></a>' ) . attr ( 'href' , url ) . text ( url ) ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-11-19 21:20:36 +01:00
}
2014-11-23 22:26:38 +01:00
if ( this . fields ) {
2015-10-29 10:55:34 +01:00
$form . append ( '<input type="submit" class="pure-button button-primary" value="' + _ _ ( 'Register' ) + '"/>' ) ;
2015-06-27 06:36:25 +02:00
$form . on ( 'submit' , this . submitRegistrationForm . bind ( this ) ) ;
2015-10-29 10:55:34 +01:00
$form . append ( '<input type="button" class="pure-button button-cancel" value="' + _ _ ( 'Cancel' ) + '"/>' ) ;
2015-06-27 06:36:25 +02:00
$form . find ( 'input[type=button]' ) . on ( 'click' , this . cancelRegistration . bind ( this ) ) ;
2014-11-23 22:26:38 +01:00
} else {
$form . append ( '<input type="button" class="submit" value="' + _ _ ( 'Return' ) + '"/>' ) ;
2015-06-27 06:36:25 +02:00
$form . find ( 'input[type=button]' ) . on ( 'click' , this . cancelRegistration . bind ( this ) ) ;
2014-11-19 21:20:36 +01:00
}
2014-11-17 13:55:52 +01:00
} ,
reportErrors : function ( stanza ) {
2014-11-17 19:10:19 +01:00
/ * R e p o r t b a c k t o t h e u s e r a n y e r r o r m e s s a g e s r e c e i v e d f r o m t h e
* XMPP server after attempted registration .
*
* Parameters :
* ( XMLElement ) stanza - The IQ stanza received from the
* XMPP server .
* /
2014-11-17 13:55:52 +01:00
var $form = this . $ ( 'form' ) , flash ;
var $errmsgs = $ ( stanza ) . find ( 'error text' ) ;
var $flash = $form . find ( '.form-errors' ) ;
if ( ! $flash . length ) {
flash = '<legend class="form-errors"></legend>' ;
if ( $form . find ( 'p.instructions' ) . length ) {
$form . find ( 'p.instructions' ) . append ( flash ) ;
} else {
$form . prepend ( flash ) ;
}
$flash = $form . find ( '.form-errors' ) ;
} else {
$flash . empty ( ) ;
}
$errmsgs . each ( function ( idx , txt ) {
$flash . append ( $ ( '<p>' ) . text ( $ ( txt ) . text ( ) ) ) ;
} ) ;
if ( ! $errmsgs . length ) {
$flash . append ( $ ( '<p>' ) . text (
_ _ ( 'The provider rejected your registration attempt. ' +
'Please check the values you entered for correctness.' ) ) ) ;
}
$flash . show ( ) ;
} ,
2014-11-19 10:09:46 +01:00
cancelRegistration : function ( ev ) {
/ * H a n d l e r , w h e n t h e u s e r c a n c e l s t h e r e g i s t r a t i o n f o r m .
2014-11-17 19:10:19 +01:00
* /
2014-11-17 13:55:52 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-11-19 21:20:36 +01:00
converse . connection . reset ( ) ;
this . render ( ) ;
2014-11-16 12:47:30 +01:00
} ,
2014-11-19 10:09:46 +01:00
submitRegistrationForm : function ( ev ) {
/ * H a n d l e r , w h e n t h e u s e r s u b m i t s t h e r e g i s t r a t i o n f o r m .
2014-11-17 19:10:19 +01:00
* Provides form error feedback or starts the registration
* process .
*
* Parameters :
* ( Event ) ev - the submit event .
* /
2014-11-17 09:44:42 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-11-24 20:35:00 +01:00
var $empty _inputs = this . $ ( 'input.required:emptyVal' ) ;
2014-11-17 13:55:52 +01:00
if ( $empty _inputs . length ) {
$empty _inputs . addClass ( 'error' ) ;
return ;
}
2014-11-17 09:44:42 +01:00
var $inputs = $ ( ev . target ) . find ( ':input:not([type=button]):not([type=submit])' ) ,
2015-04-21 17:21:26 +02:00
iq = $iq ( { type : "set" } ) . c ( "query" , { xmlns : Strophe . NS . REGISTER } ) ;
2014-11-17 09:44:42 +01:00
2015-10-25 18:49:35 +01:00
if ( this . form _type === 'xform' ) {
2015-04-21 16:05:03 +02:00
iq . c ( "x" , { xmlns : Strophe . NS . XFORM , type : 'submit' } ) ;
$inputs . each ( function ( ) {
iq . cnode ( utils . webForm2xForm ( this ) ) . up ( ) ;
} ) ;
} else {
$inputs . each ( function ( ) {
var $input = $ ( this ) ;
iq . c ( $input . attr ( 'name' ) , { } , $input . val ( ) ) ;
} ) ;
}
2014-11-17 19:10:19 +01:00
converse . connection . _addSysHandler ( this . _onRegisterIQ . bind ( this ) , null , "iq" , null , null ) ;
2014-11-17 09:44:42 +01:00
converse . connection . send ( iq ) ;
2014-11-17 19:10:19 +01:00
this . setFields ( iq . tree ( ) ) ;
} ,
setFields : function ( stanza ) {
/ * S t o r e s t h e v a l u e s t h a t w i l l b e s e n t t o t h e X M P P s e r v e r
* during attempted registration .
*
* Parameters :
* ( XMLElement ) stanza - the IQ stanza that will be sent to the XMPP server .
* /
2014-11-19 21:20:36 +01:00
var $query = $ ( stanza ) . find ( 'query' ) , $xform ;
2014-11-18 11:23:50 +01:00
if ( $query . length > 0 ) {
2014-11-19 21:20:36 +01:00
$xform = $query . find ( 'x[xmlns="' + Strophe . NS . XFORM + '"]' ) ;
if ( $xform . length > 0 ) {
this . _setFieldsFromXForm ( $xform ) ;
} else {
this . _setFieldsFromLegacy ( $query ) ;
}
2014-07-14 23:09:39 +02:00
}
2014-11-17 09:44:42 +01:00
} ,
2014-11-19 21:20:36 +01:00
_setFieldsFromLegacy : function ( $query ) {
2015-06-27 06:36:25 +02:00
$query . children ( ) . each ( function ( idx , field ) {
2014-11-23 22:26:38 +01:00
var $field = $ ( field ) ;
2014-11-19 21:20:36 +01:00
if ( field . tagName . toLowerCase ( ) === 'instructions' ) {
this . instructions = Strophe . getText ( field ) ;
return ;
} else if ( field . tagName . toLowerCase ( ) === 'x' ) {
2014-11-23 22:26:38 +01:00
if ( $field . attr ( 'xmlns' ) === 'jabber:x:oob' ) {
2015-06-27 06:36:25 +02:00
$field . find ( 'url' ) . each ( function ( idx , url ) {
2014-11-23 22:26:38 +01:00
this . urls . push ( $ ( url ) . text ( ) ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-11-23 22:26:38 +01:00
}
2014-11-19 21:20:36 +01:00
return ;
}
this . fields [ field . tagName . toLowerCase ( ) ] = Strophe . getText ( field ) ;
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-11-19 21:20:36 +01:00
this . form _type = 'legacy' ;
} ,
_setFieldsFromXForm : function ( $xform ) {
this . title = $xform . find ( 'title' ) . text ( ) ;
this . instructions = $xform . find ( 'instructions' ) . text ( ) ;
2015-06-27 06:36:25 +02:00
$xform . find ( 'field' ) . each ( function ( idx , field ) {
2014-11-20 11:51:12 +01:00
var _var = field . getAttribute ( 'var' ) ;
if ( _var ) {
this . fields [ _var . toLowerCase ( ) ] = $ ( field ) . children ( 'value' ) . text ( ) ;
} else {
// TODO: other option seems to be type="fixed"
2015-10-25 18:49:35 +01:00
converse . log ( "WARNING: Found field we couldn't parse" ) ;
2014-11-20 11:51:12 +01:00
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-11-19 21:20:36 +01:00
this . form _type = 'xform' ;
} ,
2014-11-17 19:10:19 +01:00
_onRegisterIQ : function ( stanza ) {
/ * C a l l b a c k m e t h o d t h a t g e t s c a l l e d w h e n a r e t u r n I Q s t a n z a
* is received from the XMPP server , after attempting to
* register a new user .
*
* Parameters :
* ( XMLElement ) stanza - The IQ stanza .
* /
2015-10-25 18:49:35 +01:00
var error = null ,
2014-11-17 13:55:52 +01:00
query = stanza . getElementsByTagName ( "query" ) ;
2014-11-17 09:44:42 +01:00
if ( query . length > 0 ) {
query = query [ 0 ] ;
}
if ( stanza . getAttribute ( "type" ) === "error" ) {
2014-11-17 13:55:52 +01:00
converse . log ( "Registration failed." ) ;
2014-11-17 09:44:42 +01:00
error = stanza . getElementsByTagName ( "error" ) ;
if ( error . length !== 1 ) {
2014-11-17 13:55:52 +01:00
converse . connection . _changeConnectStatus ( Strophe . Status . REGIFAIL , "unknown" ) ;
2014-11-17 09:44:42 +01:00
return false ;
}
error = error [ 0 ] . firstChild . tagName . toLowerCase ( ) ;
if ( error === 'conflict' ) {
2014-11-17 13:55:52 +01:00
converse . connection . _changeConnectStatus ( Strophe . Status . CONFLICT , error ) ;
2014-11-17 09:44:42 +01:00
} else if ( error === 'not-acceptable' ) {
2014-11-17 13:55:52 +01:00
converse . connection . _changeConnectStatus ( Strophe . Status . NOTACCEPTABLE , error ) ;
2014-11-17 09:44:42 +01:00
} else {
2014-11-17 13:55:52 +01:00
converse . connection . _changeConnectStatus ( Strophe . Status . REGIFAIL , error ) ;
2014-11-17 09:44:42 +01:00
}
2014-11-17 13:55:52 +01:00
this . reportErrors ( stanza ) ;
} else {
converse . connection . _changeConnectStatus ( Strophe . Status . REGISTERED , null ) ;
2014-11-17 09:44:42 +01:00
}
return false ;
} ,
2014-11-16 12:47:30 +01:00
remove : function ( ) {
this . $tabs . empty ( ) ;
this . $el . parent ( ) . empty ( ) ;
}
} ) ;
this . LoginPanel = Backbone . View . extend ( {
tagName : 'div' ,
id : "login-dialog" ,
className : 'controlbox-pane' ,
events : {
'submit form#converse-login' : 'authenticate'
2013-06-02 00:21:06 +02:00
} ,
2012-10-18 21:40:06 +02:00
2013-08-02 12:26:16 +02:00
initialize : function ( cfg ) {
2014-01-19 06:10:26 +01:00
cfg . $parent . html ( this . $el . html (
converse . templates . login _panel ( {
2015-04-02 13:07:59 +02:00
'LOGIN' : LOGIN ,
2015-03-22 12:14:45 +01:00
'ANONYMOUS' : ANONYMOUS ,
'PREBIND' : PREBIND ,
2015-03-22 13:20:24 +01:00
'auto_login' : converse . auto _login ,
2015-03-22 12:14:45 +01:00
'authentication' : converse . authentication ,
2014-11-16 12:47:30 +01:00
'label_username' : _ _ ( 'XMPP Username:' ) ,
2014-01-19 06:10:26 +01:00
'label_password' : _ _ ( 'Password:' ) ,
2015-03-22 12:14:45 +01:00
'label_anon_login' : _ _ ( 'Click here to log in anonymously' ) ,
2015-05-08 19:08:09 +02:00
'label_login' : _ _ ( 'Log In' ) ,
'placeholder_username' : _ _ ( 'user@server' ) ,
'placeholder_password' : _ _ ( 'password' )
2014-01-19 06:10:26 +01:00
} )
) ) ;
2013-08-02 12:26:16 +02:00
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
} ,
render : function ( ) {
2013-12-30 20:27:57 +01:00
this . $tabs . append ( converse . templates . login _tab ( { label _sign _in : _ _ ( 'Sign in' ) } ) ) ;
2013-08-02 12:26:16 +02:00
this . $el . find ( 'input#jid' ) . focus ( ) ;
2014-12-06 17:29:28 +01:00
if ( ! this . $el . is ( ':visible' ) ) {
this . $el . show ( ) ;
}
2013-08-02 12:26:16 +02:00
return this ;
} ,
2013-06-02 00:21:06 +02:00
authenticate : function ( ev ) {
2014-07-14 20:41:26 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2015-03-22 12:14:45 +01:00
var $form = $ ( ev . target ) ;
if ( converse . authentication === ANONYMOUS ) {
this . connect ( $form , converse . jid , null ) ;
return ;
}
var $jid _input = $form . find ( 'input[name=jid]' ) ,
2013-06-02 00:21:06 +02:00
jid = $jid _input . val ( ) ,
2013-09-02 11:48:09 +02:00
$pw _input = $form . find ( 'input[name=password]' ) ,
2013-06-02 00:21:06 +02:00
password = $pw _input . val ( ) ,
errors = false ;
if ( ! jid ) {
errors = true ;
$jid _input . addClass ( 'error' ) ;
2013-03-06 10:42:53 +01:00
}
2013-06-02 00:21:06 +02:00
if ( ! password ) {
errors = true ;
$pw _input . addClass ( 'error' ) ;
}
if ( errors ) { return ; }
this . connect ( $form , jid , password ) ;
2013-09-02 11:48:09 +02:00
return false ;
2013-06-02 00:21:06 +02:00
} ,
2012-07-08 12:27:13 +02:00
2014-11-16 12:47:30 +01:00
connect : function ( $form , jid , password ) {
2015-03-22 12:14:45 +01:00
var resource ;
2014-11-16 12:47:30 +01:00
if ( $form ) {
$form . find ( 'input[type=submit]' ) . hide ( ) . after ( '<span class="spinner login-submit"/>' ) ;
}
2015-03-22 12:14:45 +01:00
if ( jid ) {
resource = Strophe . getResourceFromJid ( jid ) ;
if ( ! resource ) {
2015-07-01 19:43:58 +02:00
jid = jid . toLowerCase ( ) + '/converse.js-' + Math . floor ( Math . random ( ) * 139749825 ) . toString ( ) ;
} else {
jid = Strophe . getBareJidFromJid ( jid ) . toLowerCase ( ) + '/' + Strophe . getResourceFromJid ( jid ) ;
2015-03-22 12:14:45 +01:00
}
2014-11-16 12:47:30 +01:00
}
2015-06-07 18:22:30 +02:00
converse . connection . connect ( jid , password , converse . onConnectStatusChanged ) ;
2014-11-16 12:47:30 +01:00
} ,
2013-06-02 00:21:06 +02:00
remove : function ( ) {
2013-08-02 12:26:16 +02:00
this . $tabs . empty ( ) ;
this . $el . parent ( ) . empty ( ) ;
2013-03-06 10:42:53 +01:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2013-03-06 10:42:53 +01:00
2013-10-20 22:20:45 +02:00
this . ControlBoxToggle = Backbone . View . extend ( {
tagName : 'a' ,
2014-06-05 00:12:53 +02:00
className : 'toggle-controlbox' ,
2013-10-20 22:20:45 +02:00
id : 'toggle-controlbox' ,
events : {
'click' : 'onClick'
} ,
attributes : {
'href' : "#"
} ,
initialize : function ( ) {
this . render ( ) ;
} ,
render : function ( ) {
2014-09-22 12:55:14 +02:00
$ ( '#conversejs' ) . prepend ( this . $el . html (
2014-01-19 06:10:26 +01:00
converse . templates . controlbox _toggle ( {
'label_toggle' : _ _ ( 'Toggle chat' )
} )
2014-09-22 12:55:14 +02:00
) ) ;
// We let the render method of ControlBoxView decide whether
// the ControlBox or the Toggle must be shown. This prevents
// artifacts (i.e. on page load the toggle is shown only to then
// seconds later be hidden in favor of the control box).
this . $el . hide ( ) ;
2013-10-20 22:20:45 +02:00
return this ;
} ,
2014-01-22 22:19:45 +01:00
hide : function ( callback ) {
2014-04-26 02:14:58 +02:00
this . $el . fadeOut ( 'fast' , callback ) ;
2014-01-22 22:19:45 +01:00
} ,
show : function ( callback ) {
this . $el . show ( 'fast' , callback ) ;
} ,
2013-10-20 22:20:45 +02:00
showControlBox : function ( ) {
var controlbox = converse . chatboxes . get ( 'controlbox' ) ;
if ( ! controlbox ) {
2014-09-22 12:55:14 +02:00
controlbox = converse . addControlBox ( ) ;
}
if ( converse . connection . connected ) {
controlbox . save ( { closed : false } ) ;
} else {
controlbox . trigger ( 'show' ) ;
2013-10-20 22:20:45 +02:00
}
} ,
onClick : function ( e ) {
e . preventDefault ( ) ;
if ( $ ( "div#controlbox" ) . is ( ':visible' ) ) {
var controlbox = converse . chatboxes . get ( 'controlbox' ) ;
2014-09-06 23:34:39 +02:00
if ( converse . connection . connected ) {
2014-09-22 12:55:14 +02:00
controlbox . save ( { closed : true } ) ;
2013-10-20 22:20:45 +02:00
} else {
controlbox . trigger ( 'hide' ) ;
}
} else {
this . showControlBox ( ) ;
}
}
} ) ;
2014-09-22 12:55:14 +02:00
this . addControlBox = function ( ) {
return this . chatboxes . add ( {
id : 'controlbox' ,
box _id : 'controlbox' ,
closed : ! this . show _controlbox _by _default
} ) ;
} ;
2014-11-24 22:40:10 +01:00
this . setUpXMLLogging = function ( ) {
if ( this . debug ) {
2015-10-25 18:49:35 +01:00
this . connection . xmlInput = function ( body ) { converse . log ( body ) ; } ;
this . connection . xmlOutput = function ( body ) { converse . log ( body ) ; } ;
2014-11-24 22:40:10 +01:00
}
} ;
2015-03-21 21:50:48 +01:00
this . startNewBOSHSession = function ( ) {
$ . ajax ( {
url : this . prebind _url ,
type : 'GET' ,
success : function ( response ) {
this . connection . attach (
response . jid ,
response . sid ,
response . rid ,
2015-06-07 18:22:30 +02:00
this . onConnectStatusChanged
2015-03-21 21:50:48 +01:00
) ;
} . bind ( this ) ,
error : function ( response ) {
delete this . connection ;
this . emit ( 'noResumeableSession' ) ;
} . bind ( this )
} ) ;
} ;
2015-06-05 14:46:52 +02:00
this . attemptPreboundSession = function ( tokens ) {
/ * H a n d l e s e s s i o n r e s u m p t i o n o r i n i t i a l i z a t i o n w h e n p r e b i n d i s b e i n g u s e d .
* /
if ( this . keepalive ) {
if ( ! this . jid ) {
throw new Error ( "initConnection: when using 'keepalive' with 'prebind, you must supply the JID of the current user." ) ;
}
2015-06-19 18:01:42 +02:00
try {
return this . connection . restore ( this . jid , this . onConnectStatusChanged ) ;
} catch ( e ) {
converse . log ( "Could not restore session for jid: " + this . jid + " Error message: " + e . message ) ;
2015-06-05 14:46:52 +02:00
}
} else { // Not keepalive
if ( this . jid && this . sid && this . rid ) {
2015-06-07 18:22:30 +02:00
return this . connection . attach ( this . jid , this . sid , this . rid , this . onConnectStatusChanged ) ;
2015-06-05 14:46:52 +02:00
} else {
throw new Error ( "initConnection: If you use prebind and not keepalive, " +
"then you MUST supply JID, RID and SID values" ) ;
}
}
// We haven't been able to attach yet. Let's see if there
// is a prebind_url, otherwise there's nothing with which
// we can attach.
if ( this . prebind _url ) {
this . startNewBOSHSession ( ) ;
} else {
delete this . connection ;
this . emit ( 'noResumeableSession' ) ;
}
} ;
2015-06-19 18:01:42 +02:00
this . attemptNonPreboundSession = function ( ) {
2015-06-05 14:46:52 +02:00
/ * H a n d l e s e s s i o n r e s u m p t i o n o r i n i t i a l i z a t i o n w h e n p r e b i n d i s n o t b e i n g u s e d .
*
* Two potential options exist and are handled in this method :
* 1. keepalive
* 2. auto _login
* /
2015-06-19 18:01:42 +02:00
if ( this . keepalive ) {
try {
2015-06-22 00:01:31 +02:00
return this . connection . restore ( undefined , this . onConnectStatusChanged ) ;
2015-06-19 18:01:42 +02:00
} catch ( e ) {
converse . log ( "Could not restore sessions. Error message: " + e . message ) ;
}
2015-07-11 13:49:28 +02:00
}
if ( this . auto _login ) {
2015-06-05 14:46:52 +02:00
if ( ! this . jid ) {
throw new Error ( "initConnection: If you use auto_login, you also need to provide a jid value" ) ;
}
if ( this . authentication === ANONYMOUS ) {
2015-07-01 19:43:58 +02:00
this . connection . connect ( this . jid . toLowerCase ( ) , null , this . onConnectStatusChanged ) ;
2015-06-05 14:46:52 +02:00
} else if ( this . authentication === LOGIN ) {
if ( ! this . password ) {
throw new Error ( "initConnection: If you use auto_login and " +
"authentication='login' then you also need to provide a password." ) ;
}
2015-07-01 19:43:58 +02:00
this . jid = Strophe . getBareJidFromJid ( this . jid ) . toLowerCase ( ) + '/' + Strophe . getResourceFromJid ( this . jid ) ;
2015-06-07 18:22:30 +02:00
this . connection . connect ( this . jid , this . password , this . onConnectStatusChanged ) ;
2015-06-05 14:46:52 +02:00
}
}
} ;
2014-09-06 23:34:39 +02:00
this . initConnection = function ( ) {
2014-10-19 20:41:16 +02:00
if ( this . connection && this . connection . connected ) {
2014-11-24 22:40:10 +01:00
this . setUpXMLLogging ( ) ;
2014-09-06 23:34:39 +02:00
this . onConnected ( ) ;
} else {
2015-02-07 16:54:58 +01:00
if ( ! this . bosh _service _url && ! this . websocket _url ) {
2015-03-31 15:26:57 +02:00
throw new Error ( "initConnection: you must supply a value for either the bosh_service_url or websocket_url or both." ) ;
2015-02-07 16:54:58 +01:00
}
2015-02-08 19:35:58 +01:00
if ( ( 'WebSocket' in window || 'MozWebSocket' in window ) && this . websocket _url ) {
2015-06-22 00:01:31 +02:00
this . connection = new Strophe . Connection ( this . websocket _url ) ;
2015-02-08 19:35:58 +01:00
} else if ( this . bosh _service _url ) {
2015-06-19 18:01:42 +02:00
this . connection = new Strophe . Connection ( this . bosh _service _url , { 'keepalive' : this . keepalive } ) ;
2015-02-08 19:35:58 +01:00
} else {
2015-03-31 15:26:57 +02:00
throw new Error ( "initConnection: this browser does not support websockets and bosh_service_url wasn't specified." ) ;
2014-09-06 23:34:39 +02:00
}
2014-11-24 22:40:10 +01:00
this . setUpXMLLogging ( ) ;
2015-06-05 14:46:52 +02:00
// We now try to resume or automatically set up a new session.
// Otherwise the user will be shown a login form.
2015-05-16 16:28:23 +02:00
if ( this . authentication === PREBIND ) {
2015-06-19 18:01:42 +02:00
this . attemptPreboundSession ( ) ;
2015-05-16 16:28:23 +02:00
} else {
2015-06-19 18:01:42 +02:00
this . attemptNonPreboundSession ( ) ;
2014-09-06 23:34:39 +02:00
}
}
} ;
2014-09-17 10:35:24 +02:00
this . _tearDown = function ( ) {
2014-09-18 09:29:31 +02:00
/ * R e m o v e t h o s e v i e w s w h i c h a r e o n l y a l l o w e d w i t h a v a l i d
* connection .
* /
2014-09-20 15:07:55 +02:00
this . initial _presence _sent = false ;
2014-11-24 22:40:10 +01:00
if ( this . roster ) {
this . roster . off ( ) . reset ( ) ; // Removes roster contacts
}
if ( this . rosterview ) {
2015-04-08 19:51:33 +02:00
this . rosterview . unregisterHandlers ( ) ;
2014-11-24 22:40:10 +01:00
this . rosterview . model . off ( ) . reset ( ) ; // Removes roster groups
this . rosterview . undelegateEvents ( ) . remove ( ) ;
}
2014-09-20 15:07:55 +02:00
this . chatboxes . remove ( ) ; // Don't call off(), events won't get re-registered upon reconnect.
2014-09-18 09:29:31 +02:00
if ( this . features ) {
2014-09-20 15:07:55 +02:00
this . features . reset ( ) ;
2014-09-18 09:29:31 +02:00
}
if ( this . minimized _chats ) {
2014-09-20 15:07:55 +02:00
this . minimized _chats . undelegateEvents ( ) . model . reset ( ) ;
this . minimized _chats . removeAll ( ) ; // Remove sub-views
this . minimized _chats . tearDown ( ) . remove ( ) ; // Remove overview
delete this . minimized _chats ;
2014-09-18 09:29:31 +02:00
}
return this ;
2014-09-17 10:35:24 +02:00
} ;
2014-05-27 19:18:02 +02:00
this . _initialize = function ( ) {
this . chatboxes = new this . ChatBoxes ( ) ;
this . chatboxviews = new this . ChatBoxViews ( { model : this . chatboxes } ) ;
this . controlboxtoggle = new this . ControlBoxToggle ( ) ;
this . otr = new this . OTR ( ) ;
2014-09-06 23:34:39 +02:00
this . initSession ( ) ;
this . initConnection ( ) ;
2014-09-26 17:20:52 +02:00
if ( this . connection ) {
this . addControlBox ( ) ;
}
2014-09-18 09:29:31 +02:00
return this ;
2014-05-27 19:18:02 +02:00
} ;
2015-05-10 00:51:36 +02:00
this . _overrideAttribute = function ( key , plugin ) {
// See converse.plugins.override
var value = plugin . overrides [ key ] ;
if ( typeof value === "function" ) {
2015-05-16 16:27:51 +02:00
if ( typeof plugin . _super === "undefined" ) {
plugin . _super = { 'converse' : converse } ;
}
2015-05-10 00:51:36 +02:00
plugin . _super [ key ] = converse [ key ] . bind ( converse ) ;
2015-05-10 17:15:15 +02:00
converse [ key ] = value . bind ( plugin ) ;
2015-05-10 00:51:36 +02:00
} else {
converse [ key ] = value ;
}
} ;
this . _extendObject = function ( obj , attributes ) {
// See converse.plugins.extend
if ( ! obj . prototype . _super ) {
obj . prototype . _super = { 'converse' : converse } ;
}
_ . each ( attributes , function ( value , key ) {
if ( key === 'events' ) {
obj . prototype [ key ] = _ . extend ( value , obj . prototype [ key ] ) ;
} else {
if ( typeof value === 'function' ) {
obj . prototype . _super [ key ] = obj . prototype [ key ] ;
}
obj . prototype [ key ] = value ;
}
} ) ;
} ;
2014-10-12 16:24:57 +02:00
this . _initializePlugins = function ( ) {
2015-06-27 06:36:25 +02:00
_ . each ( this . plugins , function ( plugin ) {
2015-05-10 00:51:36 +02:00
plugin . converse = converse ;
_ . each ( Object . keys ( plugin . overrides ) , function ( key ) {
/ * W e a u t o m a t i c a l l y o v e r r i d e a l l m e t h o d s a n d B a c k b o n e v i e w s a n d
* models that are in the "overrides" namespace .
* /
var override = plugin . overrides [ key ] ;
2015-10-25 18:49:35 +01:00
if ( typeof override === "object" ) {
2015-05-10 00:51:36 +02:00
this . _extendObject ( converse [ key ] , override ) ;
} else {
this . _overrideAttribute ( key , plugin ) ;
}
} . bind ( this ) ) ;
if ( typeof plugin . initialize === "function" ) {
plugin . initialize . bind ( plugin ) ( this ) ;
} else {
// This will be deprecated in 0.10
2015-06-27 06:36:25 +02:00
plugin . bind ( this ) ( this ) ;
2015-05-10 00:51:36 +02:00
}
2015-06-27 06:36:25 +02:00
} . bind ( this ) ) ;
2014-10-12 16:24:57 +02:00
} ;
2013-10-05 22:34:47 +02:00
// Initialization
// --------------
2013-06-02 00:21:06 +02:00
// This is the end of the initialize method.
2014-11-25 10:50:30 +01:00
if ( settings . connection ) {
this . connection = settings . connection ;
}
2014-10-12 16:24:57 +02:00
this . _initializePlugins ( ) ;
2014-05-27 19:18:02 +02:00
this . _initialize ( ) ;
2014-02-28 13:22:15 +01:00
this . registerGlobalEventHandlers ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'initialized' ) ;
2013-04-20 11:32:54 +02:00
} ;
2014-10-12 11:11:28 +02:00
var wrappedChatBox = function ( chatbox ) {
2015-07-17 16:49:58 +02:00
if ( ! chatbox ) { return ; }
2015-02-25 18:31:09 +01:00
var view = converse . chatboxviews . get ( chatbox . get ( 'jid' ) ) ;
2014-10-12 11:11:28 +02:00
return {
2015-06-27 06:36:25 +02:00
'close' : view . close . bind ( view ) ,
'endOTR' : chatbox . endOTR . bind ( chatbox ) ,
'focus' : view . focus . bind ( view ) ,
'get' : chatbox . get . bind ( chatbox ) ,
'initiateOTR' : chatbox . initiateOTR . bind ( chatbox ) ,
2015-06-27 08:43:26 +02:00
'is_chatroom' : chatbox . is _chatroom ,
2015-06-27 06:36:25 +02:00
'maximize' : chatbox . maximize . bind ( chatbox ) ,
'minimize' : chatbox . minimize . bind ( chatbox ) ,
2015-06-27 08:43:26 +02:00
'open' : view . show . bind ( view ) ,
2015-06-27 06:36:25 +02:00
'set' : chatbox . set . bind ( chatbox )
2014-10-12 11:11:28 +02:00
} ;
} ;
2015-04-02 02:01:53 +02:00
2015-07-17 16:49:58 +02:00
var API = {
2014-11-09 13:03:56 +01:00
'initialize' : function ( settings , callback ) {
converse . initialize ( settings , callback ) ;
} ,
2015-03-21 23:41:39 +01:00
'disconnect' : function ( ) {
converse . connection . disconnect ( ) ;
} ,
2015-03-21 21:50:48 +01:00
'account' : {
2015-06-27 21:21:27 +02:00
// XXX: Deprecated, will be removed with next non-minor release
'logout' : function ( ) {
converse . logOut ( ) ;
}
} ,
'user' : {
2015-03-21 21:50:48 +01:00
'logout' : function ( ) {
converse . logOut ( ) ;
} ,
2015-06-27 21:21:27 +02:00
'status' : {
'get' : function ( ) {
return converse . xmppstatus . get ( 'status' ) ;
} ,
'set' : function ( value , message ) {
var data = { 'status' : value } ;
if ( ! _ . contains ( _ . keys ( STATUS _WEIGHTS ) , value ) ) {
throw new Error ( 'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1' ) ;
}
2015-10-25 18:49:35 +01:00
if ( typeof message === "string" ) {
2015-06-27 21:21:27 +02:00
data . status _message = message ;
}
converse . xmppstatus . save ( data ) ;
} ,
'message' : {
'get' : function ( ) {
return converse . xmppstatus . get ( 'status_message' ) ;
} ,
'set' : function ( stat ) {
converse . xmppstatus . save ( { 'status_message' : stat } ) ;
}
}
} ,
2015-03-21 21:50:48 +01:00
} ,
2015-02-11 15:02:04 +01:00
'settings' : {
'get' : function ( key ) {
if ( _ . contains ( Object . keys ( converse . default _settings ) , key ) ) {
return converse [ key ] ;
}
} ,
'set' : function ( key , val ) {
var o = { } ;
if ( typeof key === "object" ) {
_ . extend ( converse , _ . pick ( key , Object . keys ( converse . default _settings ) ) ) ;
} else if ( typeof key === "string" ) {
o [ key ] = val ;
_ . extend ( converse , _ . pick ( o , Object . keys ( converse . default _settings ) ) ) ;
}
}
} ,
2014-11-09 13:03:56 +01:00
'contacts' : {
'get' : function ( jids ) {
var _transform = function ( jid ) {
var contact = converse . roster . get ( Strophe . getBareJidFromJid ( jid ) ) ;
if ( contact ) {
return contact . attributes ;
}
return null ;
} ;
2015-02-25 18:31:09 +01:00
if ( typeof jids === "undefined" ) {
jids = converse . roster . pluck ( 'jid' ) ;
} else if ( typeof jids === "string" ) {
2014-11-09 13:03:56 +01:00
return _transform ( jids ) ;
}
return _ . map ( jids , _transform ) ;
2015-03-29 18:12:23 +02:00
} ,
'add' : function ( jid , name ) {
2015-03-31 15:26:57 +02:00
if ( typeof jid !== "string" || jid . indexOf ( '@' ) < 0 ) {
throw new TypeError ( 'contacts.add: invalid jid' ) ;
2015-03-30 12:02:28 +02:00
}
2015-04-06 11:10:05 +02:00
converse . roster . addAndSubscribe ( jid , _ . isEmpty ( name ) ? jid : name ) ;
2014-09-22 15:03:57 +02:00
}
} ,
2014-11-09 13:03:56 +01:00
'chats' : {
2015-02-25 18:31:09 +01:00
'open' : function ( jids ) {
2015-04-02 02:01:53 +02:00
var chatbox ;
2015-02-25 18:31:09 +01:00
if ( typeof jids === "undefined" ) {
converse . log ( "chats.open: You need to provide at least one JID" , "error" ) ;
return null ;
} else if ( typeof jids === "string" ) {
2015-07-17 16:49:58 +02:00
chatbox = wrappedChatBox ( converse . chatboxes . getChatBox ( jids , true ) ) ;
2015-04-02 02:01:53 +02:00
chatbox . open ( ) ;
return chatbox ;
}
return _ . map ( jids , function ( jid ) {
2015-07-17 16:49:58 +02:00
chatbox = wrappedChatBox ( converse . chatboxes . getChatBox ( jid , true ) ) ;
2015-04-02 02:01:53 +02:00
chatbox . open ( ) ;
return chatbox ;
} ) ;
2015-02-25 18:31:09 +01:00
} ,
'get' : function ( jids ) {
if ( typeof jids === "undefined" ) {
2015-04-02 02:01:53 +02:00
converse . log ( "chats.get: You need to provide at least one JID" , "error" ) ;
return null ;
2015-02-25 18:31:09 +01:00
} else if ( typeof jids === "string" ) {
2015-07-17 16:49:58 +02:00
return wrappedChatBox ( converse . chatboxes . getChatBox ( jids , true ) ) ;
2015-02-25 18:31:09 +01:00
}
2015-07-17 16:49:58 +02:00
return _ . map ( jids , _ . partial ( _ . compose ( wrappedChatBox , converse . chatboxes . getChatBox . bind ( converse . chatboxes ) ) , _ , true ) ) ;
2014-10-12 10:34:33 +02:00
}
} ,
2015-07-10 17:26:54 +02:00
'archive' : {
'query' : function ( options , callback , errback ) {
2015-07-11 14:55:02 +02:00
/ * D o a M A M ( X E P - 0 3 1 3 ) q u e r y f o r a r c h i v e d m e s s a g e s .
*
* Parameters :
* ( Object ) options - Query parameters , either MAM - specific or also for Result Set Management .
* ( Function ) callback - A function to call whenever we receive query - relevant stanza .
* ( Function ) errback - A function to call when an error stanza is received .
*
* The options parameter can also be an instance of
* Strophe . RSM to enable easy querying between results pages .
*
* The callback function may be called multiple times , first
* for the initial IQ result and then for each message
* returned . The last time the callback is called , a
* Strophe . RSM object is returned on which "next" or "previous"
* can be called before passing it in again to this method , to
* get the next or previous page in the result set .
* /
2015-07-21 20:10:16 +02:00
var date , messages = [ ] ;
2015-10-25 18:49:35 +01:00
if ( typeof options === "function" ) {
2015-07-10 17:26:54 +02:00
callback = options ;
errback = callback ;
}
if ( ! converse . features . findWhere ( { 'var' : Strophe . NS . MAM } ) ) {
throw new Error ( 'This server does not support XEP-0313, Message Archive Management' ) ;
}
2015-07-11 13:40:02 +02:00
var queryid = converse . connection . getUniqueId ( ) ;
2015-07-21 11:38:44 +02:00
var attrs = { 'type' : 'set' } ;
2015-10-25 18:49:35 +01:00
if ( typeof options !== "undefined" && options . groupchat ) {
2015-07-21 11:38:44 +02:00
if ( ! options [ 'with' ] ) {
throw new Error ( 'You need to specify a "with" value containing the chat room JID, when querying groupchat messages.' ) ;
}
attrs . to = options [ 'with' ] ;
}
var stanza = $iq ( attrs ) . c ( 'query' , { 'xmlns' : Strophe . NS . MAM , 'queryid' : queryid } ) ;
2015-10-25 18:49:35 +01:00
if ( typeof options !== "undefined" ) {
2015-07-29 14:35:07 +02:00
stanza . c ( 'x' , { 'xmlns' : Strophe . NS . XFORM , 'type' : 'submit' } )
. c ( 'field' , { 'var' : 'FORM_TYPE' , 'type' : 'hidden' } )
2015-07-10 17:26:54 +02:00
. c ( 'value' ) . t ( Strophe . NS . MAM ) . up ( ) . up ( ) ;
2015-07-21 11:38:44 +02:00
if ( options [ 'with' ] && ! options . groupchat ) {
2015-07-11 14:55:02 +02:00
stanza . c ( 'field' , { 'var' : 'with' } ) . c ( 'value' ) . t ( options [ 'with' ] ) . up ( ) . up ( ) ;
2015-07-10 17:26:54 +02:00
}
_ . each ( [ 'start' , 'end' ] , function ( t ) {
if ( options [ t ] ) {
date = moment ( options [ t ] ) ;
if ( date . isValid ( ) ) {
stanza . c ( 'field' , { 'var' : t } ) . c ( 'value' ) . t ( date . format ( ) ) . up ( ) . up ( ) ;
} else {
throw new TypeError ( 'archive.query: invalid date provided for: ' + t ) ;
}
}
} ) ;
stanza . up ( ) ;
2015-07-17 20:11:13 +02:00
if ( options instanceof Strophe . RSM ) {
2015-07-11 14:55:02 +02:00
stanza . cnode ( options . toXML ( ) ) ;
} else if ( _ . intersection ( RSM _ATTRIBUTES , _ . keys ( options ) ) . length ) {
2015-07-11 12:03:20 +02:00
stanza . cnode ( new Strophe . RSM ( options ) . toXML ( ) ) ;
2015-07-10 17:26:54 +02:00
}
}
2015-07-21 20:10:16 +02:00
converse . connection . addHandler ( function ( message ) {
2015-10-25 18:49:35 +01:00
var $msg = $ ( message ) , $fin , rsm ;
if ( typeof callback === "function" ) {
2015-07-21 20:10:16 +02:00
$fin = $msg . find ( 'fin[xmlns="' + Strophe . NS . MAM + '"]' ) ;
if ( $fin . length ) {
rsm = new Strophe . RSM ( { xml : $fin . find ( 'set' ) [ 0 ] } ) ;
_ . extend ( rsm , _ . pick ( options , [ 'max' ] ) ) ;
_ . extend ( rsm , _ . pick ( options , MAM _ATTRIBUTES ) ) ;
callback ( messages , rsm ) ;
return false ; // We've received all messages, decommission this handler
2015-10-25 18:49:35 +01:00
} else if ( queryid === $msg . find ( 'result' ) . attr ( 'queryid' ) ) {
2015-07-21 20:10:16 +02:00
messages . push ( message ) ;
}
return true ;
} else {
return false ; // There's no callback, so no use in continuing this handler.
}
} , Strophe . NS . MAM ) ;
converse . connection . sendIQ ( stanza , null , errback ) ;
2015-07-10 17:26:54 +02:00
}
} ,
2015-04-05 08:22:10 +02:00
'rooms' : {
'open' : function ( jids , nick ) {
if ( ! nick ) {
2015-04-08 13:12:29 +02:00
nick = Strophe . getNodeFromJid ( converse . bare _jid ) ;
2015-04-05 08:22:10 +02:00
}
if ( typeof nick !== "string" ) {
throw new TypeError ( 'rooms.open: invalid nick, must be string' ) ;
}
var _transform = function ( jid ) {
var chatroom = converse . chatboxes . get ( jid ) ;
converse . log ( 'jid' ) ;
if ( ! chatroom ) {
2015-04-08 13:12:29 +02:00
chatroom = converse . chatboxviews . showChat ( {
2015-04-05 08:22:10 +02:00
'id' : jid ,
'jid' : jid ,
'name' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( jid ) ) ,
'nick' : nick ,
'chatroom' : true ,
'box_id' : b64 _sha1 ( jid )
} ) ;
}
2015-11-02 01:16:45 +01:00
return wrappedChatBox ( converse . chatboxes . getChatBox ( jid , true ) ) ;
2015-04-05 08:22:10 +02:00
} ;
if ( typeof jids === "undefined" ) {
throw new TypeError ( 'rooms.open: You need to provide at least one JID' ) ;
} else if ( typeof jids === "string" ) {
return _transform ( jids ) ;
}
return _ . map ( jids , _transform ) ;
} ,
'get' : function ( jids ) {
if ( typeof jids === "undefined" ) {
throw new TypeError ( "rooms.get: You need to provide at least one JID" ) ;
} else if ( typeof jids === "string" ) {
2015-07-17 16:49:58 +02:00
return wrappedChatBox ( converse . chatboxes . getChatBox ( jids , true ) ) ;
2015-04-05 08:22:10 +02:00
}
2015-07-17 16:49:58 +02:00
return _ . map ( jids , _ . partial ( wrappedChatBox , _ . bind ( converse . chatboxes . getChatBox , converse . chatboxes , _ , true ) ) ) ;
2015-04-05 08:22:10 +02:00
}
} ,
2014-11-09 13:03:56 +01:00
'tokens' : {
'get' : function ( id ) {
if ( ! converse . expose _rid _and _sid || typeof converse . connection === "undefined" ) {
return null ;
}
if ( id . toLowerCase ( ) === 'rid' ) {
return converse . connection . rid || converse . connection . _proto . rid ;
} else if ( id . toLowerCase ( ) === 'sid' ) {
return converse . connection . sid || converse . connection . _proto . sid ;
}
2014-02-12 11:19:12 +01:00
}
} ,
2014-11-09 13:03:56 +01:00
'listen' : {
'once' : function ( evt , handler ) {
converse . once ( evt , handler ) ;
} ,
'on' : function ( evt , handler ) {
converse . on ( evt , handler ) ;
} ,
'not' : function ( evt , handler ) {
converse . off ( evt , handler ) ;
} ,
} ,
2015-03-01 00:55:22 +01:00
'send' : function ( stanza ) {
converse . connection . send ( stanza ) ;
} ,
2015-05-27 18:14:58 +02:00
'ping' : function ( jid ) {
converse . ping ( jid ) ;
} ,
2014-11-09 13:03:56 +01:00
'plugins' : {
2015-05-10 00:51:36 +02:00
'add' : function ( name , plugin ) {
converse . plugins [ name ] = plugin ;
2014-11-09 13:03:56 +01:00
} ,
'remove' : function ( name ) {
delete converse . plugins [ name ] ;
} ,
2015-05-10 00:51:36 +02:00
'override' : function ( name , value ) {
/ * H e l p e r m e t h o d f o r o v e r r i d i n g m e t h o d s a n d a t t r i b u t e s d i r e c t l y o n t h e
* converse object . For Backbone objects , use instead the 'extend'
* method .
*
* If a method is overridden , then the original method will still be
* available via the _super attribute .
*
* name : The attribute being overridden .
* value : The value of the attribute being overridden .
* /
converse . _overrideAttribute ( name , value ) ;
} ,
2014-11-09 21:26:49 +01:00
'extend' : function ( obj , attributes ) {
2014-11-09 13:03:56 +01:00
/ * H e l p e r m e t h o d f o r o v e r r i d i n g o r e x t e n d i n g C o n v e r s e ' s B a c k b o n e V i e w s o r M o d e l s
2015-05-10 00:51:36 +02:00
*
* When a method is overriden , the original will still be available
* on the _super attribute of the object being overridden .
*
* obj : The Backbone View or Model
* attributes : A hash of attributes , such as you would pass to Backbone . Model . extend or Backbone . View . extend
* /
converse . _extendObject ( obj , attributes ) ;
2014-02-12 11:37:39 +01:00
}
} ,
2014-11-09 13:03:56 +01:00
'env' : {
2015-02-01 16:15:34 +01:00
'$build' : $build ,
'$iq' : $iq ,
'$msg' : $msg ,
2015-06-22 22:05:33 +02:00
'$pres' : $pres ,
'Strophe' : Strophe ,
2015-02-01 16:15:34 +01:00
'_' : _ ,
2015-06-22 22:05:33 +02:00
'b64_sha1' : b64 _sha1 ,
'jQuery' : $ ,
'moment' : moment
2014-10-26 15:52:27 +01:00
}
2013-08-05 09:25:29 +02:00
} ;
2015-07-17 16:49:58 +02:00
return API ;
2012-09-21 16:04:57 +02:00
} ) ) ;