2012-07-22 16:44:01 +02:00
/ * !
2013-04-25 23:51:55 +02:00
* Converse . js ( Web - based XMPP instant messaging client )
2013-04-18 22:13:28 +02:00
* http : //conversejs.org
2012-07-22 16:44:01 +02:00
*
2013-04-25 23:51:55 +02:00
* Copyright ( c ) 2012 , Jan - Carel Brand < jc @ opkode . com >
2014-06-02 04:47:23 +02:00
* Licensed under the Mozilla Public License ( MPL )
2012-07-22 16:44:01 +02:00
* /
2012-09-21 16:04:57 +02:00
// AMD/global registrations
( function ( root , factory ) {
2013-02-20 17:21:07 +01:00
if ( typeof define === 'function' && define . amd ) {
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 ) {
2014-10-13 22:02:55 +02:00
var otr = dependencies . otr ;
2013-12-30 20:27:57 +01:00
if ( typeof otr !== "undefined" ) {
2014-10-13 22:02:55 +02:00
return factory (
dependencies . jQuery ,
_ ,
otr . OTR ,
otr . DSA ,
templates ,
dependencies . moment ,
dependencies . utils
) ;
2013-12-30 20:27:57 +01:00
} else {
2014-10-13 22:02:55 +02:00
return factory (
dependencies . jQuery ,
_ ,
undefined ,
undefined ,
templates ,
dependencies . moment ,
dependencies . utils
) ;
2013-12-30 20:27:57 +01:00
}
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 {
2014-10-13 22:02:55 +02:00
root . converse = factory ( jQuery , _ , OTR , DSA , JST , moment , utils ) ;
2012-09-21 16:04:57 +02:00
}
2014-10-13 22:02:55 +02:00
} ( this , function ( $ , _ , OTR , DSA , templates , moment , utils ) {
2014-08-04 19:38:48 +02:00
// "use strict";
// Cannot use this due to Safari bug.
// See https://github.com/jcbrand/converse.js/issues/196
2014-01-22 23:53:50 +01:00
if ( typeof console === "undefined" || typeof console . log === "undefined" ) {
console = { log : function ( ) { } , error : function ( ) { } } ;
}
2014-09-20 15:07:55 +02:00
2014-09-17 23:05:17 +02:00
// Configuration of underscore templates (this config is distict to the
// config of requirejs-tpl in main.js). This one is for normal inline
// templates.
// Use Mustache style syntax for variable interpolation
_ . 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 {
throw new Error ( 'Wrong attribute type. Must be string or array.' ) ;
}
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
} ;
} ;
2014-10-13 21:15:25 +02:00
// XXX: these can perhaps be moved to src/polyfills.js
2014-09-05 19:36:31 +02:00
String . prototype . splitOnce = function ( delimiter ) {
var components = this . split ( delimiter ) ;
return [ components . shift ( ) , components . join ( delimiter ) ] ;
} ;
2014-10-10 11:05:16 +02:00
$ . fn . addEmoticons = function ( ) {
2014-04-25 22:56:59 +02:00
if ( converse . visible _toolbar _buttons . emoticons ) {
2013-12-18 03:15:27 +01:00
if ( this . length > 0 ) {
2014-10-10 11:05:16 +02:00
this . each ( function ( i , obj ) {
2013-12-18 03:15:27 +01:00
var text = $ ( obj ) . html ( ) ;
2014-05-19 16:06:11 +02:00
text = text . replace ( />:\)/g , '<span class="emoticon icon-evil"></span>' ) ;
2013-12-18 03:15:27 +01:00
text = text . replace ( /:\)/g , '<span class="emoticon icon-smiley"></span>' ) ;
text = text . replace ( /:\-\)/g , '<span class="emoticon icon-smiley"></span>' ) ;
text = text . replace ( /;\)/g , '<span class="emoticon icon-wink"></span>' ) ;
text = text . replace ( /;\-\)/g , '<span class="emoticon icon-wink"></span>' ) ;
text = text . replace ( /:D/g , '<span class="emoticon icon-grin"></span>' ) ;
text = text . replace ( /:\-D/g , '<span class="emoticon icon-grin"></span>' ) ;
text = text . replace ( /:P/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /:\-P/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /:p/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /:\-p/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /8\)/g , '<span class="emoticon icon-cool"></span>' ) ;
text = text . replace ( /:S/g , '<span class="emoticon icon-confused"></span>' ) ;
text = text . replace ( /:\\/g , '<span class="emoticon icon-wondering"></span>' ) ;
text = text . replace ( /:\/ /g , '<span class="emoticon icon-wondering"></span>' ) ;
text = text . replace ( />:\(/g , '<span class="emoticon icon-angry"></span>' ) ;
text = text . replace ( /:\(/g , '<span class="emoticon icon-sad"></span>' ) ;
text = text . replace ( /:\-\(/g , '<span class="emoticon icon-sad"></span>' ) ;
text = text . replace ( /:O/g , '<span class="emoticon icon-shocked"></span>' ) ;
text = text . replace ( /:\-O/g , '<span class="emoticon icon-shocked"></span>' ) ;
text = text . replace ( /\=\-O/g , '<span class="emoticon icon-shocked"></span>' ) ;
text = text . replace ( /\(\^.\^\)b/g , '<span class="emoticon icon-thumbs-up"></span>' ) ;
2014-05-19 16:06:11 +02:00
text = text . replace ( /<3/g , '<span class="emoticon icon-heart"></span>' ) ;
2013-12-18 03:15:27 +01:00
$ ( obj ) . html ( text ) ;
} ) ;
}
}
return this ;
} ;
2014-10-13 21:15:25 +02:00
var playNotification = function ( ) {
var audio ;
if ( converse . play _sounds && typeof Audio !== "undefined" ) {
audio = new Audio ( "sounds/msg_received.ogg" ) ;
if ( audio . canPlayType ( '/audio/ogg' ) ) {
audio . play ( ) ;
} else {
audio = new Audio ( "/sounds/msg_received.mp3" ) ;
audio . play ( ) ;
}
}
} ;
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 ,
* otherwise chatboxes are not moved along when one is closed .
* /
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
} ;
2013-08-24 03:10:06 +02:00
converse . initialize = function ( settings , callback ) {
2013-10-03 14:24:23 +02:00
var converse = this ;
2013-09-08 15:54:04 +02:00
// Constants
2013-10-05 22:38:14 +02:00
// ---------
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 = {
ENTER : 13
} ;
2014-07-24 20:48:52 +02:00
var STATUS _WEIGHTS = {
'offline' : 6 ,
'unavailable' : 5 ,
'xa' : 4 ,
'away' : 3 ,
'dnd' : 2 ,
'online' : 1
} ;
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' ;
2014-08-20 21:00:28 +02: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" )
) ;
2013-09-08 15:54:04 +02:00
2014-08-02 14:25:24 +02:00
var OPENED = 'opened' ;
var CLOSED = 'closed' ;
2013-10-05 22:34:47 +02:00
// Default configuration values
// ----------------------------
2013-10-03 14:22:33 +02:00
this . allow _contact _requests = true ;
2014-02-22 22:20:36 +01:00
this . allow _dragresize = true ;
2014-09-07 13:18:36 +02:00
this . allow _logout = true ;
2013-10-03 14:24:23 +02:00
this . allow _muc = true ;
this . allow _otr = true ;
2013-06-02 00:21:06 +02:00
this . animate = true ;
this . auto _list _rooms = false ;
2014-08-23 10:55:04 +02:00
this . auto _reconnect = false ;
2013-06-02 00:21:06 +02:00
this . auto _subscribe = false ;
2013-08-24 02:43:41 +02:00
this . bosh _service _url = undefined ; // The BOSH connection manager URL.
2014-01-31 17:07:44 +01:00
this . cache _otr _key = false ;
2013-08-15 19:34:40 +02:00
this . debug = false ;
2014-03-01 00:51:07 +01:00
this . default _box _height = 324 ; // The default height, in pixels, for the control box, chat boxes and chatrooms.
2014-02-12 11:37:39 +01:00
this . expose _rid _and _sid = false ;
2014-04-19 06:18:17 +02:00
this . forward _messages = false ;
2013-06-02 00:21:06 +02:00
this . hide _muc _server = false ;
2013-06-02 21:40:05 +02:00
this . i18n = locales . en ;
2014-09-18 09:33:51 +02:00
this . keepalive = false ;
2014-07-19 19:14:29 +02:00
this . message _carbons = false ;
2014-05-27 19:18:02 +02:00
this . no _trimming = false ; // Set to true for phantomjs tests (where browser apparently has no width)
2014-08-20 21:00:28 +02:00
this . play _sounds = false ;
2013-06-02 00:21:06 +02:00
this . prebind = false ;
2014-07-19 19:14:29 +02:00
this . roster _groups = false ;
2013-06-02 21:40:05 +02:00
this . show _controlbox _by _default = false ;
2013-10-05 23:07:42 +02:00
this . show _only _online _users = false ;
2013-09-08 16:55:40 +02:00
this . show _toolbar = true ;
2014-06-30 18:53:58 +02:00
this . storage = 'session' ;
2013-12-19 00:01:06 +01:00
this . use _otr _by _default = false ;
2013-12-17 18:24:36 +01:00
this . use _vcards = true ;
2014-04-24 18:03:30 +02:00
this . visible _toolbar _buttons = {
'emoticons' : true ,
'call' : false ,
2014-09-03 20:04:32 +02:00
'clear' : true ,
2014-09-03 20:15:46 +02:00
'toggle_participants' : true
2014-04-24 18:03:30 +02:00
} ;
2013-09-02 11:30:54 +02:00
this . xhr _custom _status = false ;
2013-10-20 18:34:20 +02:00
this . xhr _custom _status _url = '' ;
2013-09-02 11:30:54 +02:00
this . xhr _user _search = false ;
2013-10-20 18:34:20 +02:00
this . xhr _user _search _url = '' ;
2013-09-02 11:30:54 +02:00
2013-10-05 22:34:47 +02:00
// Allow only whitelisted configuration attributes to be overwritten
_ . extend ( this , _ . pick ( settings , [
2013-10-03 14:22:33 +02:00
'allow_contact_requests' ,
2014-02-22 22:20:36 +01:00
'allow_dragresize' ,
2014-09-07 13:18:36 +02:00
'allow_logout' ,
2013-10-03 13:53:32 +02:00
'allow_muc' ,
'allow_otr' ,
2013-08-24 02:43:41 +02:00
'animate' ,
'auto_list_rooms' ,
2014-02-11 12:14:36 +01:00
'auto_reconnect' ,
2013-08-24 02:43:41 +02:00
'auto_subscribe' ,
'bosh_service_url' ,
2014-01-31 17:07:44 +01:00
'cache_otr_key' ,
2013-09-02 11:30:54 +02:00
'connection' ,
2013-08-24 02:43:41 +02:00
'debug' ,
2014-03-01 00:51:07 +01:00
'default_box_height' ,
2014-09-06 23:34:39 +02:00
'keepalive' ,
2014-07-19 16:36:43 +02:00
'message_carbons' ,
2014-02-12 11:37:39 +01:00
'expose_rid_and_sid' ,
2014-04-19 06:18:17 +02:00
'forward_messages' ,
2013-09-02 11:30:54 +02:00
'fullname' ,
2013-08-24 02:43:41 +02:00
'hide_muc_server' ,
'i18n' ,
2013-09-02 11:30:54 +02:00
'jid' ,
2014-07-19 19:14:29 +02:00
'no_trimming' ,
2014-08-20 21:00:28 +02:00
'play_sounds' ,
2013-08-24 02:43:41 +02:00
'prebind' ,
2013-09-02 11:30:54 +02:00
'rid' ,
2014-07-19 19:14:29 +02:00
'roster_groups' ,
2013-08-24 02:43:41 +02:00
'show_controlbox_by_default' ,
2013-10-05 23:07:42 +02:00
'show_only_online_users' ,
2013-09-08 16:55:40 +02:00
'show_toolbar' ,
2013-08-26 16:40:34 +02:00
'sid' ,
2014-06-30 18:53:58 +02:00
'storage' ,
2013-12-19 00:01:06 +01:00
'use_otr_by_default' ,
2013-12-17 18:24:36 +01:00
'use_vcards' ,
2013-09-02 11:30:54 +02:00
'xhr_custom_status' ,
2013-10-20 18:34:20 +02:00
'xhr_custom_status_url' ,
'xhr_user_search' ,
'xhr_user_search_url'
2013-10-05 22:34:47 +02:00
] ) ) ;
2014-04-24 19:14:37 +02:00
if ( settings . visible _toolbar _buttons ) {
_ . extend (
this . visible _toolbar _buttons ,
_ . pick ( settings . visible _toolbar _buttons , [
2014-09-03 20:15:46 +02:00
'emoticons' , 'call' , 'clear' , 'toggle_participants'
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 machinery
// ---------------------
2014-10-29 17:13:54 +01:00
var _ _ = $ . proxy ( utils . _ _ , this ) ;
2014-10-13 22:02:55 +02:00
var _ _ _ = utils . _ _ _ ;
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
// ----------------------
2013-08-23 00:48:53 +02:00
this . giveFeedback = function ( message , klass ) {
2014-08-08 17:59:31 +02:00
$ ( '.conn-feedback' ) . attr ( 'class' , 'conn-feedback' ) . text ( message ) ;
2013-08-23 00:48:53 +02:00
if ( klass ) {
$ ( '.conn-feedback' ) . addClass ( klass ) ;
}
} ;
2013-09-08 15:54:04 +02:00
this . log = function ( txt , level ) {
2013-08-22 21:43:34 +02:00
if ( this . debug ) {
2013-09-08 15:54:04 +02:00
if ( level == 'error' ) {
console . log ( 'ERROR: ' + txt ) ;
} else {
console . log ( txt ) ;
}
2013-08-22 21:43:34 +02:00
}
} ;
2013-10-05 22:34:47 +02:00
this . getVCard = function ( jid , callback , errback ) {
2013-12-17 18:24:36 +01:00
if ( ! this . use _vcards ) {
if ( callback ) {
callback ( jid , jid ) ;
}
return ;
}
2013-10-05 22:34:47 +02:00
converse . connection . vcard . get (
$ . proxy ( 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
} ) ;
}
}
if ( callback ) {
callback ( jid , fullname , img , img _type , url ) ;
}
} , this ) ,
jid ,
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 ) {
contact . save ( {
2014-04-25 22:56:59 +02:00
'vcard_updated' : moment ( ) . format ( )
2013-10-05 22:34:47 +02:00
} ) ;
}
if ( errback ) {
2014-09-18 18:51:23 +02:00
errback ( jid , iq ) ;
2013-10-05 22:34:47 +02:00
}
2013-12-17 18:24:36 +01:00
}
) ;
2013-10-05 22:34:47 +02:00
} ;
2014-02-11 12:14:36 +01:00
this . reconnect = function ( ) {
converse . giveFeedback ( _ _ ( 'Reconnecting' ) , 'error' ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'reconnect' ) ;
2014-02-11 22:48:38 +01:00
if ( ! converse . prebind ) {
2014-02-11 12:14:36 +01:00
this . connection . connect (
this . connection . jid ,
this . connection . pass ,
2014-02-11 12:44:27 +01:00
function ( status , condition ) {
converse . onConnect ( status , condition , true ) ;
} ,
2014-02-11 12:14:36 +01:00
this . connection . wait ,
this . connection . hold ,
this . connection . route
) ;
}
} ;
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
} ;
2014-02-11 12:44:27 +01:00
this . onConnect = function ( status , condition , reconnect ) {
2013-10-07 09:08:11 +02:00
var $button , $form ;
2014-02-11 12:14:36 +01:00
if ( ( status === Strophe . Status . CONNECTED ) ||
( status === Strophe . Status . ATTACHED ) ) {
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 ) {
converse . giveFeedback ( _ _ ( 'Disconnected' ) , 'error' ) ;
2014-02-11 12:14:36 +01:00
if ( converse . auto _reconnect ) {
converse . reconnect ( ) ;
} else {
2014-09-18 18:51:23 +02:00
converse . renderLoginPanel ( ) ;
2014-02-11 12:14:36 +01:00
}
2013-08-23 00:48:53 +02:00
} else if ( status === Strophe . Status . Error ) {
2014-09-18 18:51:23 +02:00
converse . renderLoginPanel ( ) ;
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 . CONNFAIL ) {
2014-09-18 18:51:23 +02:00
converse . renderLoginPanel ( ) ;
2013-08-23 00:48:53 +02:00
converse . giveFeedback ( _ _ ( 'Connection Failed' ) , 'error' ) ;
} else if ( status === Strophe . Status . AUTHENTICATING ) {
converse . giveFeedback ( _ _ ( 'Authenticating' ) ) ;
} else if ( status === Strophe . Status . AUTHFAIL ) {
2014-09-18 18:51:23 +02:00
converse . renderLoginPanel ( ) ;
2013-08-23 00:48:53 +02:00
converse . giveFeedback ( _ _ ( 'Authentication Failed' ) , 'error' ) ;
} else if ( status === Strophe . Status . DISCONNECTING ) {
2014-09-18 18:51:23 +02:00
if ( ! converse . connection . connected ) {
converse . renderLoginPanel ( ) ;
2014-09-20 15:07:55 +02:00
} else {
converse . giveFeedback ( _ _ ( 'Disconnecting' ) , 'error' ) ;
2014-09-18 18:51:23 +02:00
}
2013-08-23 00:48:53 +02:00
}
} ;
2014-03-01 00:51:07 +01:00
this . applyHeightResistance = function ( height ) {
/ * 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 / g r a v i t y a r o u n d t h e
* "default_box_height" . If "height" is close enough to
* default _box _height , then that is returned instead .
* /
if ( typeof height === 'undefined' ) {
return converse . default _box _height ;
}
var resistance = 10 ;
2014-03-04 14:48:16 +01:00
if ( ( height !== converse . default _box _height ) &&
2014-03-01 00:51:07 +01:00
( Math . abs ( height - converse . default _box _height ) < resistance ) ) {
return converse . default _box _height ;
}
return height ;
} ;
2013-06-02 00:21:06 +02:00
this . updateMsgCounter = function ( ) {
if ( this . msg _counter > 0 ) {
if ( document . title . search ( /^Messages \(\d+\) / ) == - 1 ) {
document . title = "Messages (" + this . msg _counter + ") " + document . title ;
} else {
document . title = document . title . replace ( /^Messages \(\d+\) / , "Messages (" + this . msg _counter + ") " ) ;
}
window . blur ( ) ;
window . focus ( ) ;
} else if ( document . title . search ( /^Messages \(\d+\) / ) != - 1 ) {
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 ( ) {
this . session = new this . BOSHSession ( ) ;
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 ( ) ;
$ ( window ) . on ( 'beforeunload' , $ . proxy ( function ( ) {
2014-09-07 00:18:36 +02:00
if ( converse . connection . connected ) {
this . setSession ( ) ;
} else {
2014-09-07 13:18:36 +02:00
this . clearSession ( ) ;
2014-09-07 00:18:36 +02:00
}
2014-09-06 23:34:39 +02:00
} , this ) ) ;
} ;
2014-09-07 13:18:36 +02:00
this . clearSession = function ( ) {
2014-10-27 21:53:05 +01:00
this . roster . browserStorage . _clear ( ) ;
2014-09-07 13:18:36 +02:00
this . session . browserStorage . _clear ( ) ;
// XXX: this should perhaps go into the beforeunload handler
converse . chatboxes . get ( 'controlbox' ) . save ( { 'connected' : false } ) ;
} ;
2014-09-06 23:34:39 +02:00
this . setSession = function ( ) {
if ( this . keepalive ) {
this . session . save ( {
jid : this . connection . jid ,
rid : this . connection . _proto . rid ,
sid : this . connection . _proto . sid
} ) ;
}
} ;
2014-09-07 13:18:36 +02:00
this . logOut = function ( ) {
converse . chatboxviews . closeAllChatBoxes ( false ) ;
converse . clearSession ( ) ;
converse . connection . disconnect ( ) ;
2014-09-20 15:07:55 +02:00
converse . connection . reset ( ) ;
2014-09-07 13:18:36 +02:00
} ;
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 ( ) ;
}
} ) ;
2014-02-23 01:54:35 +01:00
$ ( document ) . on ( 'mousemove' , $ . proxy ( function ( ev ) {
if ( ! this . resized _chatbox || ! this . allow _dragresize ) { return true ; }
ev . preventDefault ( ) ;
2014-02-28 13:22:15 +01:00
this . resized _chatbox . resizeChatBox ( ev ) ;
2014-02-23 01:54:35 +01:00
} , this ) ) ;
$ ( document ) . on ( 'mouseup' , $ . proxy ( function ( ev ) {
if ( ! this . resized _chatbox || ! this . allow _dragresize ) { return true ; }
2014-03-01 00:51:07 +01:00
ev . preventDefault ( ) ;
var height = this . applyHeightResistance ( this . resized _chatbox . height ) ;
2014-09-06 23:34:39 +02:00
if ( this . connection . connected ) {
2014-03-01 00:51:07 +01:00
this . resized _chatbox . model . save ( { 'height' : height } ) ;
2014-02-28 13:22:15 +01:00
} else {
2014-03-01 00:51:07 +01:00
this . resized _chatbox . model . set ( { 'height' : height } ) ;
2014-02-28 13:22:15 +01:00
}
2014-02-23 01:54:35 +01:00
this . resized _chatbox = null ;
} , this ) ) ;
2014-06-14 16:22:52 +02:00
$ ( window ) . on ( "blur focus" , $ . proxy ( function ( ev ) {
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 ;
2014-01-31 04:50:38 +01:00
} , this ) ) ;
2014-06-14 16:22:52 +02:00
$ ( window ) . on ( "resize" , _ . debounce ( $ . proxy ( function ( ev ) {
this . chatboxviews . trimChats ( ) ;
} , this ) , 200 ) ) ;
2014-01-31 04:50:38 +01: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.
this . initStatus ( $ . proxy ( function ( ) {
this . registerRosterXHandler ( ) ;
this . registerPresenceHandler ( ) ;
this . chatboxes . registerMessageHandler ( ) ;
converse . xmppstatus . sendPresence ( ) ;
this . giveFeedback ( _ _ ( 'Online Contacts' ) ) ;
} , this ) ) ;
} ;
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
* /
2014-07-19 16:36:43 +02:00
if ( ! this . message _carbons ) {
2014-04-19 06:58:26 +02:00
return ;
}
var carbons _iq = new Strophe . Builder ( 'iq' , {
from : this . connection . jid ,
id : 'enablecarbons' ,
type : 'set'
} )
. c ( 'enable' , { xmlns : 'urn:xmpp:carbons:2' } ) ;
this . connection . send ( carbons _iq ) ;
2014-10-10 11:05:16 +02:00
this . connection . addHandler ( function ( iq ) {
2014-04-19 06:58:26 +02:00
//TODO: check if carbons was enabled:
} , null , "iq" , null , "enablecarbons" ) ;
} ;
2013-10-05 22:34:47 +02:00
this . onConnected = function ( ) {
if ( this . debug ) {
this . connection . xmlInput = function ( body ) { console . log ( body ) ; } ;
this . connection . xmlOutput = function ( body ) { console . log ( body ) ; } ;
2014-10-24 18:55:32 +02:00
Strophe . log = function ( level , msg ) {
console . log ( level + ' ' + msg ) ;
} ;
2014-07-14 21:44:18 +02:00
Strophe . error = function ( msg ) {
2014-06-30 18:53:58 +02:00
console . log ( 'ERROR: ' + msg ) ;
} ;
2013-10-05 22:34:47 +02:00
}
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-06 23:34:39 +02:00
this . setSession ( ) ;
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 ) ;
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 ( ) ;
2013-10-05 22:34:47 +02:00
this . initStatus ( $ . proxy ( function ( ) {
2014-08-07 22:38:52 +02:00
2013-10-05 22:34:47 +02:00
this . chatboxes . onConnected ( ) ;
this . giveFeedback ( _ _ ( 'Online 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.
//
// TODO: We might need to consider websockets, which
// probably won't use the 'service' attr. Current
// strophe.js version used by converse.js doesn't support
// websockets.
this . callback ( this ) ;
} else {
this . callback ( ) ;
}
2013-10-05 22:34:47 +02:00
}
} , 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 ( ) {
if ( converse . 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 ;
}
} ,
2014-02-12 10:30:05 +01:00
generatePrivateKey : function ( ) {
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
}
} ) ;
2014-07-25 08:56:54 +02:00
this . Message = Backbone . Model ;
2013-06-02 00:21:06 +02:00
this . Messages = Backbone . Collection . extend ( {
model : converse . Message
} ) ;
this . ChatBox = Backbone . Model . extend ( {
initialize : function ( ) {
2014-03-01 07:21:36 +01:00
var height = converse . applyHeightResistance ( this . get ( 'height' ) ) ;
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 ) ) ;
2014-02-28 19:55:46 +01:00
this . save ( {
2014-04-19 05:12:24 +02:00
'box_id' : b64 _sha1 ( this . get ( 'jid' ) ) ,
2014-06-29 15:59:39 +02:00
'height' : height ,
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 ( ) ,
2014-06-29 15:59:39 +02:00
'user_id' : Strophe . getNodeFromJid ( this . get ( 'jid' ) ) ,
2014-08-04 22:19:18 +02:00
'num_unread' : this . get ( 'num_unread' ) || 0 ,
'url' : ''
2013-03-21 13:11:45 +01:00
} ) ;
2014-03-01 07:21:36 +01:00
} else {
2014-05-05 23:37:52 +02:00
this . set ( {
'height' : height ,
2014-06-29 15:59:39 +02:00
'time_opened' : moment ( 0 ) . valueOf ( ) ,
'num_unread' : this . get ( 'num_unread' ) || 0
2014-05-05 23:37:52 +02:00
} ) ;
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 ;
2014-02-12 06:12:00 +01:00
var result , 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 ( ) {
callback ( {
'key' : converse . otr . generatePrivateKey . apply ( this ) ,
'instance_tag' : OTR . makeInstanceTag ( )
} ) ;
} , 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 ( _ _ (
2013-09-10 22:52:15 +02:00
'Authentication request from %1$s\n\nYour buddy is attempting to verify your identity, by asking you the question below.\n\n%2$s' ,
[ 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 :
throw new Error ( 'Unknown type.' ) ;
}
} ,
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
// query message from our buddy. Otherwise, it is us who will
// send the query message to them.
2013-09-10 22:37:12 +02:00
this . save ( { 'otr_status' : UNENCRYPTED } ) ;
2014-01-31 04:50:38 +01:00
var session = this . getSession ( $ . proxy ( function ( session ) {
this . otr = new OTR ( {
fragment _size : 140 ,
send _interval : 200 ,
priv : session . key ,
instance _tag : session . instance _tag ,
debug : this . debug
} ) ;
this . otr . on ( 'status' , $ . proxy ( this . updateOTRStatus , this ) ) ;
this . otr . on ( 'smp' , $ . proxy ( this . onSMP , this ) ) ;
2013-09-01 21:37:22 +02:00
2014-01-31 04:50:38 +01:00
this . otr . on ( 'ui' , $ . proxy ( function ( msg ) {
this . trigger ( 'showReceivedOTRMessage' , msg ) ;
} , this ) ) ;
this . otr . on ( 'io' , $ . proxy ( function ( msg ) {
this . trigger ( 'sendMessageStanza' , msg ) ;
} , this ) ) ;
this . otr . on ( 'error' , $ . proxy ( function ( msg ) {
this . trigger ( 'showOTRError' , msg ) ;
} , this ) ) ;
2013-09-08 18:13:49 +02:00
2014-01-31 04:50:38 +01:00
this . trigger ( 'showHelpMessages' , [ _ _ ( 'Exchanging private key with buddy.' ) ] ) ;
if ( query _msg ) {
this . otr . receiveMsg ( query _msg ) ;
} else {
this . otr . sendQueryMsg ( ) ;
}
2013-08-16 16:05:24 +02:00
} , 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 } ) ;
} ,
2014-04-19 06:58:26 +02:00
createMessage : function ( $message ) {
var body = $message . children ( 'body' ) . text ( ) ,
2013-06-02 00:21:06 +02:00
composing = $message . find ( 'composing' ) ,
2014-08-20 21:00:28 +02:00
paused = $message . find ( 'paused' ) ,
2013-06-02 00:21:06 +02:00
delayed = $message . find ( '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' ) ,
2014-09-12 22:23:42 +02:00
stamp , time , sender , from ;
if ( is _groupchat ) {
from = Strophe . unescapeNode ( Strophe . getResourceFromJid ( $message . attr ( 'from' ) ) ) ;
} else {
from = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ;
}
2013-11-04 14:57:22 +01:00
fullname = ( _ . isEmpty ( fullname ) ? from : fullname ) . split ( ' ' ) [ 0 ] ;
2013-06-02 00:21:06 +02:00
if ( ! body ) {
2014-08-20 21:00:28 +02:00
if ( composing . length || paused . length ) {
2014-10-30 11:48:58 +01:00
// FIXME: use one attribute for chat states (e.g.
// chatstate) instead of saving 'paused' and
// 'composing' separately.
2013-06-02 00:21:06 +02:00
this . messages . add ( {
fullname : fullname ,
sender : 'them' ,
delayed : delayed ,
2014-04-25 22:56:59 +02:00
time : moment ( ) . format ( ) ,
2014-08-20 21:00:28 +02:00
composing : composing . length ,
paused : paused . length
2013-06-02 00:21:06 +02:00
} ) ;
}
2013-03-21 13:11:45 +01:00
} else {
2013-06-02 00:21:06 +02:00
if ( delayed ) {
stamp = $message . find ( 'delay' ) . attr ( 'stamp' ) ;
time = stamp ;
} else {
2014-04-25 22:56:59 +02:00
time = moment ( ) . format ( ) ;
2013-06-02 00:21:06 +02:00
}
2014-09-12 22:23:42 +02:00
if ( ( is _groupchat && from === this . get ( 'nick' ) ) || ( ! is _groupchat && from == converse . bare _jid ) ) {
2013-06-02 00:21:06 +02:00
sender = 'me' ;
} else {
sender = 'them' ;
}
this . messages . create ( {
fullname : fullname ,
sender : sender ,
delayed : delayed ,
time : time ,
2014-10-15 19:49:16 +02:00
message : body ,
msgid : msgid
2013-06-02 00:21:06 +02:00
} ) ;
2013-03-21 13:11:45 +01:00
}
2013-08-24 23:09:54 +02:00
} ,
2014-04-19 06:58:26 +02:00
receiveMessage : function ( $message ) {
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 ) ) {
2014-04-19 06:58:26 +02:00
return this . createMessage ( $message ) ;
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.
2014-04-19 06:58:26 +02:00
this . createMessage ( $message ) ;
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' ,
'mousedown .dragresize-tm' : 'onDragResizeStart'
2013-06-02 00:21:06 +02:00
} ,
2013-08-16 16:05:24 +02:00
initialize : function ( ) {
2013-08-25 12:06:53 +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 ) ;
this . model . on ( 'change' , this . onChange , this ) ;
2013-08-31 15:45:23 +02:00
this . model . on ( 'showOTRError' , this . showOTRError , this ) ;
2013-08-30 21:18:46 +02:00
this . model . on ( 'buddyStartsOTR' , this . buddyStartsOTR , this ) ;
2013-08-31 15:00:04 +02:00
this . model . on ( 'showHelpMessages' , this . showHelpMessages , this ) ;
2013-08-25 12:17:46 +02:00
this . model . on ( 'sendMessageStanza' , this . sendMessageStanza , this ) ;
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 ) ;
2014-05-31 21:26:18 +02:00
2013-08-16 16:05:24 +02:00
this . updateVCard ( ) ;
2014-04-24 07:58:35 +02:00
this . $el . insertAfter ( converse . chatboxviews . get ( "controlbox" ) . $el ) ;
2014-07-17 17:29:05 +02:00
this . render ( ) . model . messages . fetch ( { add : true } ) ;
2014-06-01 20:09:09 +02:00
if ( this . model . get ( 'minimized' ) ) {
this . hide ( ) ;
} else {
this . show ( ) ;
2013-08-16 16:05:24 +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 ,
label _personal _message : _ _ ( 'Personal message' )
}
)
)
) ;
2013-08-30 22:49:33 +02:00
this . renderToolbar ( ) . renderAvatar ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxOpened' , this ) ;
2014-04-24 07:58:35 +02:00
setTimeout ( function ( ) {
converse . refreshWebkit ( ) ;
} , 50 ) ;
2014-06-02 04:47:23 +02:00
return this . showStatusMessage ( ) ;
2013-08-30 22:49:33 +02:00
} ,
2014-02-28 19:55:46 +01:00
initDragResize : function ( ) {
this . prev _pageY = 0 ; // To store last known mouse position
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' ) ;
}
2014-03-01 00:51:07 +01:00
return this ;
2014-02-28 19:55:46 +01:00
} ,
2014-09-05 19:36:31 +02:00
showStatusNotification : function ( message , keep _old ) {
2013-08-25 12:06:53 +02:00
var $chat _content = this . $el . find ( '.chat-content' ) ;
2014-09-05 19:36:31 +02:00
if ( ! keep _old ) {
$chat _content . find ( 'div.chat-event' ) . remove ( ) ;
}
$chat _content . append ( $ ( '<div class="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 ) {
this . $el . find ( '.chat-content' ) . empty ( ) ;
}
return this ;
} ,
2014-03-05 00:23:45 +01:00
showMessage : function ( msg _dict ) {
2014-03-09 12:10:57 +01:00
var $content = this . $el . find ( '.chat-content' ) ,
2014-04-25 22:56:59 +02:00
msg _time = moment ( msg _dict . time ) || moment ,
2013-06-02 00:21:06 +02:00
text = msg _dict . message ,
match = text . match ( /^\/(.*?)(?: (.*))?$/ ) ,
2014-03-09 12:10:57 +01:00
fullname = msg _dict . fullname || this . model . get ( 'fullname' ) , // XXX Perhaps always use model's?
2014-09-17 21:40:19 +02:00
extra _classes = msg _dict . delayed && 'delayed' || '' ,
2013-06-02 00:21:06 +02:00
template , username ;
2013-07-24 22:58:53 +02:00
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 ;
2014-03-05 07:44:01 +01:00
username = msg _dict . sender === 'me' && _ _ ( 'me' ) || fullname ;
2013-06-02 00:21:06 +02:00
}
2014-03-09 12:10:57 +01:00
$content . find ( 'div.chat-event' ) . remove ( ) ;
2014-09-17 21:40:19 +02:00
if ( this . is _chatroom && msg _dict . sender == 'them' && ( new RegExp ( "\\b" + this . model . get ( 'nick' ) + "\\b" ) ) . test ( text ) ) {
// Add special class to mark groupchat messages in which we
// are mentioned.
extra _classes += ' mentioned' ;
}
2013-12-18 03:15:27 +01:00
var message = template ( {
2014-03-05 00:23:45 +01:00
'sender' : msg _dict . sender ,
2014-04-25 22:56:59 +02:00
'time' : msg _time . format ( 'hh:mm' ) ,
2013-12-18 03:15:27 +01:00
'username' : username ,
'message' : '' ,
2014-09-17 21:40:19 +02:00
'extra_classes' : extra _classes
2013-12-18 03:15:27 +01:00
} ) ;
2014-03-09 12:10:57 +01:00
$content . append ( $ ( message ) . children ( '.chat-message-content' ) . first ( ) . text ( text ) . addHyperlinks ( ) . addEmoticons ( ) . parent ( ) ) ;
this . scrollDown ( ) ;
2013-06-02 00:21:06 +02:00
} ,
2014-01-31 12:40:33 +01:00
showHelpMessages : function ( msgs , type , spinner ) {
2013-08-25 12:06:53 +02:00
var $chat _content = this . $el . find ( '.chat-content' ) , i ,
msgs _length = msgs . length ;
for ( i = 0 ; i < msgs _length ; i ++ ) {
2013-08-31 15:00:04 +02:00
$chat _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 ) {
$chat _content . append ( '<span class="spinner"/>' ) ;
} else if ( spinner === false ) {
$chat _content . find ( 'span.spinner' ) . remove ( ) ;
}
2013-08-31 15:45:23 +02:00
return this . scrollDown ( ) ;
2013-08-25 12:06:53 +02:00
} ,
onMessageAdded : function ( message ) {
2013-06-02 00:21:06 +02:00
var time = message . get ( 'time' ) ,
times = this . model . messages . pluck ( 'time' ) ,
2014-04-19 01:30:48 +02:00
previous _message , idx , this _date , prev _date , text , match ;
2013-06-02 00:21:06 +02:00
// If this message is on a different day than the one received
// prior, then indicate it on the chatbox.
idx = _ . indexOf ( times , time ) - 1 ;
if ( idx >= 0 ) {
previous _message = this . model . messages . at ( idx ) ;
2014-04-19 01:30:48 +02:00
prev _date = moment ( previous _message . get ( 'time' ) ) ;
if ( prev _date . isBefore ( time , 'day' ) ) {
this _date = moment ( time ) ;
2014-03-05 00:23:45 +01:00
this . $el . find ( '.chat-content' ) . append ( converse . templates . new _day ( {
2014-04-19 01:30:48 +02:00
isodate : this _date . format ( "YYYY-MM-DD" ) ,
2014-04-25 22:56:59 +02:00
datestring : this _date . format ( "dddd MMM Do YYYY" )
2013-06-02 00:21:06 +02:00
} ) ) ;
}
2012-12-06 11:49:03 +01:00
}
2014-08-20 21:00:28 +02:00
if ( message . get ( COMPOSING ) ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'is typing' ) ) ;
return ;
} else if ( message . get ( PAUSED ) ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'has stopped typing' ) ) ;
2012-12-06 14:33:43 +01:00
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
}
2013-06-02 00:21:06 +02:00
if ( ( message . get ( 'sender' ) != 'me' ) && ( converse . windowState == 'blur' ) ) {
converse . incrementMsgCounter ( ) ;
2012-12-06 14:33:43 +01:00
}
2013-08-31 15:45:23 +02:00
return this . scrollDown ( ) ;
2013-06-02 00:21:06 +02:00
} ,
2012-09-21 16:04:57 +02:00
2013-08-25 12:17:46 +02:00
sendMessageStanza : function ( text ) {
/ *
* Sends the actual XML stanza to the XMPP server .
* /
// TODO: Look in ChatPartners to see what resources we have for the recipient.
// if we have one resource, we sent to only that resources, if we have multiple
// we send to the bare jid.
var timestamp = ( new Date ( ) ) . getTime ( ) ;
var bare _jid = this . model . get ( 'jid' ) ;
var message = $msg ( { from : converse . connection . jid , to : bare _jid , type : 'chat' , id : timestamp } )
. c ( 'body' ) . t ( text ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) ;
converse . connection . send ( message ) ;
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.
var forwarded = $msg ( { to : converse . bare _jid , type : 'chat' , id : timestamp } )
. c ( 'forwarded' , { xmlns : 'urn:xmpp:forward:0' } )
. c ( 'delay' , { xmns : 'urn:xmpp:delay' , stamp : timestamp } ) . up ( )
. cnode ( message . tree ( ) ) ;
converse . connection . send ( forwarded ) ;
}
2013-08-25 12:17:46 +02:00
} ,
2013-06-02 00:21:06 +02:00
sendMessage : function ( text ) {
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 ;
2013-08-24 23:09:54 +02:00
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
} ) ;
2013-08-25 12:17:46 +02:00
this . sendMessageStanza ( text ) ;
2013-08-16 16:05:24 +02:00
}
} ,
2013-06-02 00:21:06 +02:00
keyPressed : function ( ev ) {
var $textarea = $ ( ev . target ) ,
message , notify , composing ;
2013-09-18 09:25:40 +02: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' ) ) {
this . sendChatRoomMessage ( message ) ;
} else {
this . sendMessage ( message ) ;
}
2014-07-06 18:31:17 +02:00
converse . emit ( 'messageSend' , message ) ;
2013-06-02 00:21:06 +02:00
}
this . $el . data ( 'composing' , false ) ;
} else if ( ! this . model . get ( 'chatroom' ) ) {
// composing data is only for single user chat
composing = this . $el . data ( 'composing' ) ;
if ( ! composing ) {
if ( ev . keyCode != 47 ) {
// We don't send composing messages if the message
// starts with forward-slash.
notify = $msg ( { 'to' : this . model . get ( 'jid' ) , 'type' : 'chat' } )
. c ( 'composing' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) ;
converse . connection . send ( notify ) ;
}
this . $el . data ( 'composing' , true ) ;
2013-02-21 13:42:30 +01:00
}
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
2014-02-22 22:20:36 +01:00
onDragResizeStart : function ( ev ) {
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 ( ) ;
2014-02-22 22:20:36 +01:00
converse . resized _chatbox = this ;
2014-02-23 02:38:26 +01:00
this . prev _pageY = ev . pageY ;
2014-02-22 22:20:36 +01:00
} ,
2014-02-28 13:22:15 +01:00
setChatBoxHeight : function ( height ) {
2014-02-28 19:55:46 +01:00
if ( ! this . model . get ( 'minimized' ) ) {
2014-03-01 00:51:07 +01:00
this . $el . children ( '.box-flyout' ) [ 0 ] . style . height = converse . applyHeightResistance ( height ) + 'px' ;
2014-02-28 19:55:46 +01:00
}
2014-02-28 13:22:15 +01:00
} ,
resizeChatBox : function ( ev ) {
2014-02-23 02:38:26 +01:00
var diff = ev . pageY - this . prev _pageY ;
2014-02-22 22:20:36 +01:00
if ( ! diff ) { return ; }
2014-02-23 02:38:26 +01:00
this . height -= diff ;
this . prev _pageY = ev . pageY ;
2014-03-01 00:51:07 +01:00
this . setChatBoxHeight ( this . height ) ;
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 ) {
this . $el . find ( '.chat-content' ) . empty ( ) ;
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 ) {
if ( msg == 'Message cannot be sent at this time.' ) {
this . showHelpMessages (
[ _ _ ( 'Your message could not be sent' ) ] , 'error' ) ;
} else if ( msg == 'Received an unencrypted message.' ) {
this . showHelpMessages (
[ _ _ ( 'We received an unencrypted message' ) ] , 'error' ) ;
2013-09-10 22:52:15 +02: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' ) ;
}
console . log ( "OTR ERROR:" + msg ) ;
} ,
2013-08-30 21:18:46 +02:00
buddyStartsOTR : function ( ev ) {
2013-08-30 22:49:33 +02:00
this . showHelpMessages ( [ _ _ ( 'This user has requested an encrypted session.' ) ] ) ;
2013-08-31 23:42:48 +02:00
this . model . initiateOTR ( ) ;
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-01-28 02:50:51 +01:00
alert ( _ _ ( 'You will be prompted to provide a security question and then an answer to that question.\n\nYour buddy 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
} ) ;
} ,
2013-06-02 00:21:06 +02:00
onChange : function ( item , changed ) {
if ( _ . has ( item . changed , 'chat_status' ) ) {
var chat _status = item . get ( 'chat_status' ) ,
fullname = item . get ( 'fullname' ) ;
2013-11-04 14:57:22 +01:00
fullname = _ . isEmpty ( fullname ) ? item . get ( 'jid' ) : fullname ;
2013-06-02 00:21:06 +02:00
if ( this . $el . is ( ':visible' ) ) {
if ( chat _status === 'offline' ) {
2013-08-25 12:06:53 +02:00
this . showStatusNotification ( fullname + ' ' + 'has gone offline' ) ;
2013-06-02 00:21:06 +02:00
} else if ( chat _status === 'away' ) {
2013-08-25 12:06:53 +02:00
this . showStatusNotification ( fullname + ' ' + 'has gone away' ) ;
2013-06-02 00:21:06 +02:00
} else if ( ( chat _status === 'dnd' ) ) {
2013-08-25 12:06:53 +02:00
this . showStatusNotification ( fullname + ' ' + 'is busy' ) ;
2013-06-02 00:21:06 +02:00
} else if ( chat _status === 'online' ) {
this . $el . find ( 'div.chat-event' ) . remove ( ) ;
}
2013-03-24 10:48:12 +01:00
}
2014-07-06 18:31:17 +02:00
converse . emit ( 'buddyStatusChanged' , item . attributes , item . get ( 'chat_status' ) ) ;
2013-10-20 22:20:45 +02:00
}
2013-08-30 22:49:33 +02:00
if ( _ . has ( item . changed , 'status' ) ) {
2014-06-01 20:09:09 +02:00
this . showStatusMessage ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'buddyStatusMessageChanged' , item . attributes , item . get ( 'status' ) ) ;
2013-10-20 22:20:45 +02:00
}
2013-08-30 22:49:33 +02:00
if ( _ . has ( item . changed , 'image' ) ) {
2013-06-02 00:21:06 +02:00
this . renderAvatar ( ) ;
}
2013-08-30 22:49:33 +02:00
if ( _ . has ( item . changed , 'otr_status' ) ) {
2013-08-31 15:45:23 +02:00
this . renderToolbar ( ) . informOTRChange ( ) ;
2013-08-30 22:49:33 +02:00
}
2014-06-01 20:09:09 +02:00
if ( _ . has ( item . changed , 'minimized' ) ) {
if ( item . get ( 'minimized' ) ) {
this . hide ( ) ;
2014-05-27 22:51:11 +02:00
} else {
2014-06-01 20:09:09 +02:00
this . maximize ( ) ;
2014-05-27 22:51:11 +02:00
}
2014-05-27 18:34:22 +02:00
}
2013-06-02 00:21:06 +02:00
// TODO check for changed fullname as well
} ,
showStatusMessage : function ( msg ) {
2014-06-01 20:09:09 +02:00
msg = msg || this . model . get ( 'status' ) ;
if ( msg ) {
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 ( ) ;
} else {
this . model . trigger ( 'hide' ) ;
}
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 ( ) {
2014-06-08 21:43:00 +02:00
// Restores a minimized chat box
this . $el . insertAfter ( converse . chatboxviews . get ( "controlbox" ) . $el ) . show ( 'fast' , $ . proxy ( function ( ) {
converse . refreshWebkit ( ) ;
this . focus ( ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'chatBoxMaximized' , this ) ;
2014-06-08 21:43:00 +02:00
} , 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
this . 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 ( ) {
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 ,
$ . proxy ( function ( jid , fullname , image , image _type , url ) {
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
} ) ;
} , this ) ,
$ . proxy ( function ( stanza ) {
2013-08-22 21:43:34 +02:00
converse . log ( "ChatBoxView.initialize: An error occured while fetching vcard" ) ;
2013-06-02 00:21:06 +02:00
} , this )
) ;
}
} ,
2013-08-31 15:45:23 +02:00
informOTRChange : function ( ) {
var data = this . model . toJSON ( ) ;
var msgs = [ ] ;
if ( data . otr _status == UNENCRYPTED ) {
msgs . push ( _ _ ( "Your messages are not encrypted anymore" ) ) ;
} else if ( data . otr _status == UNVERIFIED ) {
2013-08-31 16:06:47 +02:00
msgs . push ( _ _ ( "Your messages are now encrypted but your buddy's identity has not been verified." ) ) ;
2013-08-31 15:45:23 +02:00
} else if ( data . otr _status == VERIFIED ) {
2013-08-31 16:06:47 +02:00
msgs . push ( _ _ ( "Your buddy's identify has been verified." ) ) ;
2013-08-31 15:45:23 +02:00
} else if ( data . otr _status == FINISHED ) {
msgs . push ( _ _ ( "Your buddy has ended encryption on their end, you should do the same." ) ) ;
}
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 ( ) ;
if ( data . otr _status == UNENCRYPTED ) {
data . otr _tooltip = _ _ ( 'Your messages are not encrypted. Click here to enable OTR encryption.' ) ;
} else if ( data . otr _status == UNVERIFIED ) {
data . otr _tooltip = _ _ ( 'Your messages are encrypted, but your buddy has not been verified.' ) ;
} else if ( data . otr _status == VERIFIED ) {
data . otr _tooltip = _ _ ( 'Your messages are encrypted and your buddy verified.' ) ;
} else if ( data . otr _status == FINISHED ) {
data . otr _tooltip = _ _ ( 'Your buddy has closed their end of the private session, you should do the same' ) ;
}
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' ) ,
2014-09-03 20:04:32 +02:00
label _hide _participants : _ _ ( 'Hide the list of participants' ) ,
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 ,
2014-09-03 20:15:46 +02:00
show _participants _toggle : this . is _chatroom && converse . visible _toolbar _buttons . toggle _participants
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-05-11 20:08:36 +02:00
canvas = $ ( '<canvas height="31px" width="31px" 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 ;
ctx . drawImage ( img , 0 , 0 , 35 * ratio , 35 ) ;
} ;
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 ( ) {
2013-12-15 15:58:46 +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 ) {
2013-06-02 00:21:06 +02: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
}
2014-04-26 02:14:58 +02:00
this . $el . fadeIn ( callback ) ;
2014-09-06 23:34:39 +02:00
if ( converse . connection . connected ) {
2013-06-02 00:21:06 +02:00
// Without a connection, we haven't yet initialized
// localstorage
this . model . save ( ) ;
2014-06-01 20:09:09 +02:00
this . initDragResize ( ) ;
2013-06-02 00:21:06 +02:00
}
return this ;
} ,
scrollDown : function ( ) {
2014-06-02 21:51:30 +02:00
var $content = this . $ ( '.chat-content' ) ;
if ( $content . is ( ':visible' ) ) {
$content . scrollTop ( $content [ 0 ] . scrollHeight ) ;
}
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 ( {
label _contact _username : _ _ ( 'Contact username' ) ,
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>' )
. attr ( 'data-recipient' , Strophe . escapeNode ( obj . id ) + '@' + converse . domain )
. 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 ;
}
2013-12-17 18:24:36 +01:00
this . addContact ( 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 ( ) ;
this . addContact ( jid , name ) ;
$target . parent ( ) . remove ( ) ;
$ ( '.search-xmpp' ) . hide ( ) ;
} ,
addContact : function ( jid , name ) {
2013-12-17 18:24:36 +01:00
name = _ . isEmpty ( name ) ? jid : name ;
2013-06-02 00:21:06 +02:00
converse . connection . roster . add ( jid , name , [ ] , function ( iq ) {
converse . connection . roster . subscribe ( jid , null , converse . xmppstatus . get ( 'fullname' ) ) ;
} ) ;
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' ,
id : 'chatrooms' ,
events : {
'submit form.add-chatroom' : 'createChatRoom' ,
'click input#show-rooms' : 'showRooms' ,
'click a.open-room' : 'createChatRoom' ,
'click a.room-info' : 'showRoomInfo'
} ,
2013-08-02 12:26:16 +02:00
initialize : function ( cfg ) {
cfg . $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' ,
'label_room_name' : _ _ ( 'Room name' ) ,
'label_nickname' : _ _ ( 'Nickname' ) ,
'label_server' : _ _ ( 'Server' ) ,
'label_join' : _ _ ( 'Join' ) ,
'label_show_rooms' : _ _ ( 'Show rooms' )
2013-06-02 00:21:06 +02:00
} )
) . hide ( ) ) ;
2013-08-02 12:26:16 +02:00
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
2012-10-25 23:33:09 +02:00
2013-06-02 00:21:06 +02:00
this . on ( 'update-rooms-list' , function ( ev ) {
this . updateRoomsList ( ) ;
2013-04-20 00:38:54 +02:00
} ) ;
2013-06-02 00:21:06 +02:00
converse . xmppstatus . on ( "change" , $ . proxy ( function ( model ) {
if ( ! ( _ . has ( model . changed , 'fullname' ) ) ) {
return ;
}
var $nick = this . $el . find ( 'input.new-chatroom-nick' ) ;
if ( ! $nick . is ( ':focus' ) ) {
$nick . val ( model . get ( 'fullname' ) ) ;
2013-05-13 23:38:37 +02:00
}
2013-04-20 15:13:42 +02:00
} , this ) ) ;
2013-06-02 00:21:06 +02:00
} ,
2013-08-02 12:26:16 +02:00
render : function ( ) {
2013-12-30 20:27:57 +01:00
this . $tabs . append ( converse . templates . chatrooms _tab ( { label _rooms : _ _ ( 'Rooms' ) } ) ) ;
2013-08-02 12:26:16 +02:00
return this ;
} ,
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
$available _chatrooms . html ( '<dt>' + _ _ ( 'No rooms on %1$s' , this . muc _domain ) + '</dt>' ) ;
$ ( 'input#show-rooms' ) . show ( ) . siblings ( 'span.spinner' ) . remove ( ) ;
} ,
updateRoomsList : function ( domain ) {
converse . connection . muc . listRooms (
this . muc _domain ,
$ . proxy ( function ( iq ) { // Success
var name , jid , i , fragment ,
that = this ,
$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 . 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' ) ;
2014-01-19 05:21:49 +01:00
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 ] ) ;
2013-06-02 00:21:06 +02:00
}
$available _chatrooms . append ( fragment ) ;
$ ( 'input#show-rooms' ) . show ( ) . siblings ( 'span.spinner' ) . remove ( ) ;
} else {
this . informNoRoomsFound ( ) ;
}
return true ;
} , this ) ,
$ . proxy ( function ( iq ) { // Failure
this . informNoRoomsFound ( ) ;
} , this ) ) ;
} ,
showRooms : function ( ev ) {
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"/>' ) ;
this . muc _domain = server ;
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 ,
$ . proxy ( function ( stanza ) {
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' ) ,
'label_semi_anon' : _ ( 'Semi-anonymous' ) ,
'label_temp_room' : _ ( 'Temporary room' ) ,
'label_unmoderated' : _ _ ( 'Unmoderated' )
2013-06-02 00:21:06 +02:00
} ) ) ;
} , 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' ) {
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 ( ) ;
name = $name . val ( ) . trim ( ) . toLowerCase ( ) ;
$name . val ( '' ) ; // Clear the input
if ( name && server ) {
jid = Strophe . escapeNode ( name ) + '@' + server ;
$name . removeClass ( 'error' ) ;
$server . removeClass ( 'error' ) ;
this . muc _domain = server ;
} 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 ,
'name' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( jid ) ) ,
'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
} ) ;
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' ,
'mousedown .dragresize-tm' : 'onDragResizeStart'
2013-06-02 00:21:06 +02:00
} ,
initialize : function ( ) {
2014-04-24 07:58:35 +02:00
this . $el . insertAfter ( converse . controlboxtoggle . $el ) ;
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 ( ) ;
}
} ,
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
var feature = converse . features . findWhere ( { 'var' : 'http://jabber.org/protocol/muc' } ) ;
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-10-27 18:48:54 +01:00
// TODO:
2014-10-26 17:10:58 +01:00
// See if we shouldn't also fetch the roster here... otherwise
// the roster is always populated by the rosterHandler method,
// which appears to be a less economic way.
// i.e. from what it seems, only groups are fetched from
// browserStorage, and no contacts.
2014-10-26 20:20:05 +01:00
// XXX: Make sure that if fetch is called, we don't sort on
// each item add...
2014-10-26 17:10:58 +01:00
// converse.roster.fetch()
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-02-28 13:22:15 +01:00
render : function ( ) {
2014-09-07 13:18:36 +02:00
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 ( ) ;
2014-02-28 13:22:15 +01:00
}
return this ;
} ,
2014-09-07 13:18:36 +02:00
renderLoginPanel : function ( ) {
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 ) ;
} else {
this . loginpanel . delegateEvents ( ) . initialize ( cfg ) ;
}
2014-09-07 13:18:36 +02:00
this . loginpanel . render ( ) ;
this . initDragResize ( ) ;
} ,
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 ) {
this . roomspanel = new converse . RoomsPanel ( { '$parent' : this . $el . find ( '.controlbox-panes' ) } ) ;
this . roomspanel . render ( ) ;
}
this . initDragResize ( ) ;
} ,
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 ( ) {
2014-01-22 23:33:55 +01:00
converse . controlboxtoggle . hide ( $ . proxy ( 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 ) ;
2014-01-22 23:33:55 +01:00
} , this ) ) ;
2014-01-22 22:19:45 +01:00
return this ;
} ,
2013-06-02 00:21:06 +02:00
featureAdded : function ( feature ) {
2013-10-03 18:43:49 +02:00
if ( ( feature . get ( 'var' ) == 'http://jabber.org/protocol/muc' ) && ( converse . allow _muc ) ) {
2013-06-02 00:21:06 +02:00
this . roomspanel . muc _domain = feature . get ( 'from' ) ;
var $server = this . $el . find ( 'input.new-chatroom-server' ) ;
if ( ! $server . is ( ':focus' ) ) {
$server . val ( this . roomspanel . muc _domain ) ;
}
if ( converse . auto _list _rooms ) {
this . roomspanel . trigger ( 'update-rooms-list' ) ;
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 ) {
ev . preventDefault ( ) ;
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 ( ) ;
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 ( ) {
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' ) ,
'desc_participant' : _ _ ( 'This user can send messages in this room' ) ,
'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 ( {
model : converse . ChatRoomOccupant ,
initialize : function ( options ) {
this . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
b64 _sha1 ( 'converse.occupants' + converse . bare _jid + options . nick ) ) ;
2014-10-26 15:52:27 +01:00
}
2014-09-12 15:23:21 +02:00
} ) ;
this . ChatRoomOccupantsView = Backbone . Overview . extend ( {
tagName : 'div' ,
className : 'participants' ,
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 ( ) ;
}
this . $ ( '.participant-list' ) . append ( view . render ( ) . $el ) ;
} ,
onChatRoomRoster : function ( roster , room ) {
var roster _size = _ . size ( roster ) ,
$participant _list = this . $ ( '.participant-list' ) ,
participants = [ ] ,
keys = _ . keys ( roster ) ,
occupant , attrs , i , nick ;
2014-09-12 19:46:52 +02:00
2014-09-12 15:23:21 +02:00
for ( i = 0 ; i < roster _size ; i ++ ) {
nick = Strophe . unescapeNode ( keys [ i ] ) ;
attrs = {
'id' : nick ,
'role' : roster [ keys [ i ] ] . role ,
'nick' : nick
} ;
occupant = this . model . get ( nick ) ;
if ( occupant ) {
occupant . save ( attrs ) ;
} else {
this . model . create ( attrs ) ;
}
}
2014-09-12 19:46:52 +02:00
_ . each ( _ . difference ( this . model . pluck ( 'id' ) , keys ) , function ( id ) {
this . model . get ( id ) . destroy ( ) ;
} , this ) ;
2014-09-12 15:23:21 +02:00
return true ;
} ,
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>' )
}
} ) ;
$el . on ( 'typeahead:selected' , $ . proxy ( function ( ev , suggestion , dname ) {
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 ) {
2014-09-12 19:31:53 +02:00
converse . connection . muc . rooms [ this . chatroomview . model . get ( 'id' ) ] . directInvite ( suggestion . jid , reason ) ;
2014-09-12 15:23:21 +02:00
converse . emit ( 'roomInviteSent' , this , suggestion . jid , reason ) ;
}
$ ( ev . target ) . typeahead ( 'val' , '' ) ;
} , this ) ) ;
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' ,
className : 'chatroom' ,
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' ,
2014-09-17 22:04:47 +02:00
'click .toggle-participants a' : 'toggleOccupants' ,
2014-03-01 00:51:07 +01:00
'keypress textarea.chat-textarea' : 'keyPressed' ,
'mousedown .dragresize-tm' : 'onDragResizeStart'
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 ( ) {
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 ) ;
2014-02-28 03:04:52 +01:00
this . model . on ( 'destroy' , function ( model , response , options ) {
this . hide ( ) ;
converse . connection . muc . leave (
this . model . get ( 'jid' ) ,
this . model . get ( 'nick' ) ,
$ . proxy ( this . onLeave , this ) ,
undefined ) ;
} ,
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
} ) ;
2014-09-12 19:31:53 +02:00
this . occupantsview . chatroomview = this ;
2014-09-12 15:23:21 +02:00
this . render ( ) ;
this . occupantsview . model . fetch ( { add : true } ) ;
this . connect ( null ) ;
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 ) ;
2014-09-12 15:23:21 +02:00
this . model . messages . fetch ( { add : true } ) ;
2014-06-02 04:47:23 +02:00
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 ( ) ;
2014-07-17 19:18:13 +02:00
setTimeout ( function ( ) {
converse . refreshWebkit ( ) ;
} , 50 ) ;
2014-09-12 15:23:21 +02:00
return this ;
} ,
renderChatArea : function ( ) {
if ( ! this . $ ( '.chat-area' ) . length ) {
this . $ ( '.chat-body' ) . empty ( )
. 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 ( ) ;
}
2014-09-17 22:04:47 +02:00
// XXX: This is a bit of a hack, to make sure that the
// sidebar's state is remembered.
this . model . set ( { hidden _occupants : ! this . model . get ( 'hidden_occupants' ) } ) ;
this . toggleOccupants ( ) ;
2014-02-28 03:04:52 +01:00
return this ;
} ,
2014-09-17 22:04:47 +02:00
toggleOccupants : function ( ev ) {
if ( ev ) {
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
}
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' ) ;
2014-10-19 15:09:29 +02:00
this . $ ( 'form.sendXMPPMessage, .chat-area' ) . animate ( { width : '100%' } ) ;
this . $ ( 'div.participants' ) . animate ( { width : 0 } , $ . proxy ( function ( ) {
2014-09-18 09:33:51 +02:00
this . scrollDown ( ) ;
} , this ) ) ;
2014-09-17 22:04:47 +02:00
} else {
this . model . save ( { hidden _occupants : false } ) ;
$el . removeClass ( 'icon-show-users' ) . addClass ( 'icon-hide-users' ) ;
2014-10-19 15:09:29 +02:00
this . $ ( '.chat-area, form.sendXMPPMessage' ) . css ( { width : '' } ) ;
this . $ ( 'div.participants' ) . show ( ) . animate ( { width : 'auto' } , $ . proxy ( function ( ) {
2014-09-18 09:33:51 +02:00
this . scrollDown ( ) ;
2014-09-17 22:04:47 +02:00
} , this ) ) ;
}
} ,
2014-09-05 19:36:31 +02:00
onCommandError : function ( stanza ) {
this . showStatusNotification ( _ _ ( "Error: could not execute the command" ) , true ) ;
} ,
2014-10-15 22:28:43 +02:00
createChatRoomMessage : function ( text ) {
var fullname = converse . xmppstatus . get ( 'fullname' ) ;
this . model . messages . create ( {
fullname : _ . isEmpty ( fullname ) ? converse . bare _jid : fullname ,
sender : 'me' ,
time : moment ( ) . format ( ) ,
message : text ,
msgid : converse . connection . muc . groupchat ( this . model . get ( 'jid' ) , text , undefined , String ( ( new Date ( ) ) . getTime ( ) ) )
} ) ;
} ,
2014-10-13 21:15:25 +02:00
sendChatRoomMessage : function ( text ) {
2014-10-15 22:28:43 +02:00
var match = text . replace ( /^\s*/ , "" ) . match ( /^\/(.*?)(?: (.*))?$/ ) || [ false ] , args ;
2013-06-02 00:21:06 +02:00
switch ( match [ 1 ] ) {
2014-09-05 18:27:32 +02:00
case 'ban' :
2014-09-05 19:36:31 +02:00
args = match [ 2 ] . splitOnce ( ' ' ) ;
converse . connection . muc . ban ( this . model . get ( 'jid' ) , args [ 0 ] , args [ 1 ] , undefined , $ . proxy ( this . onCommandError , 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' :
2014-09-05 19:36:31 +02:00
args = match [ 2 ] . splitOnce ( ' ' ) ;
converse . connection . muc . deop ( this . model . get ( 'jid' ) , args [ 0 ] , args [ 1 ] , undefined , $ . proxy ( this . onCommandError , this ) ) ;
2014-09-05 18:27:32 +02:00
break ;
case 'help' :
2014-10-13 21:15:25 +02:00
this . showHelpMessages ( [
2014-09-05 18:27:32 +02:00
'<strong>/ban</strong>: ' + _ _ ( 'Ban user from room' ) ,
'<strong>/clear</strong>: ' + _ _ ( 'Remove messages' ) ,
'<strong>/help</strong>: ' + _ _ ( 'Show this menu' ) ,
'<strong>/kick</strong>: ' + _ _ ( 'Kick user from room' ) ,
'<strong>/me</strong>: ' + _ _ ( 'Write in 3rd person' ) ,
'<strong>/mute</strong>: ' + _ _ ( "Remove user's ability to post messages" ) ,
'<strong>/nick</strong>: ' + _ _ ( 'Change your nickname' ) ,
'<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' :
2014-09-05 19:36:31 +02:00
args = match [ 2 ] . splitOnce ( ' ' ) ;
converse . connection . muc . kick ( this . model . get ( 'jid' ) , args [ 0 ] , args [ 1 ] , undefined , $ . proxy ( this . onCommandError , this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
2014-09-05 18:27:32 +02:00
case 'mute' :
2014-09-05 19:36:31 +02:00
args = match [ 2 ] . splitOnce ( ' ' ) ;
converse . connection . muc . mute ( this . model . get ( 'jid' ) , args [ 0 ] , args [ 1 ] , undefined , $ . proxy ( this . onCommandError , this ) ) ;
2014-09-05 18:27:32 +02:00
break ;
case 'nick' :
converse . connection . muc . changeNick ( this . model . get ( 'jid' ) , match [ 2 ] ) ;
2013-06-02 00:21:06 +02:00
break ;
case 'op' :
2014-09-05 19:36:31 +02:00
args = match [ 2 ] . splitOnce ( ' ' ) ;
converse . connection . muc . op ( this . model . get ( 'jid' ) , args [ 0 ] , args [ 1 ] , undefined , $ . proxy ( this . onCommandError , this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
2014-09-05 18:27:32 +02:00
case 'topic' :
converse . connection . muc . setTopic ( this . model . get ( 'jid' ) , match [ 2 ] ) ;
2013-06-02 00:21:06 +02:00
break ;
2014-09-05 18:27:32 +02:00
case 'voice' :
2014-09-05 19:36:31 +02:00
args = match [ 2 ] . splitOnce ( ' ' ) ;
converse . connection . muc . voice ( this . model . get ( 'jid' ) , args [ 0 ] , args [ 1 ] , undefined , $ . proxy ( this . onCommandError , this ) ) ;
2013-06-02 00:21:06 +02:00
break ;
default :
2014-10-15 22:28:43 +02:00
this . createChatRoomMessage ( 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
} ,
connect : function ( password ) {
if ( _ . has ( converse . connection . muc . rooms , this . model . get ( 'jid' ) ) ) {
// If the room exists, it already has event listeners, so we
2014-09-20 22:59:29 +02:00
// don't add them again.
2013-06-02 00:21:06 +02:00
converse . connection . muc . join (
this . model . get ( 'jid' ) , this . model . get ( 'nick' ) , null , null , null , password ) ;
} else {
converse . connection . muc . join (
this . model . get ( 'jid' ) ,
this . model . get ( 'nick' ) ,
$ . proxy ( this . onChatRoomMessage , this ) ,
$ . proxy ( this . onChatRoomPresence , this ) ,
$ . proxy ( this . onChatRoomRoster , this ) ,
password ) ;
}
} ,
onLeave : function ( ) {
this . model . set ( 'connected' , false ) ;
} ,
renderConfigurationForm : function ( stanza ) {
var $form = this . $el . find ( 'form.chatroom-form' ) ,
$stanza = $ ( stanza ) ,
$fields = $stanza . find ( 'field' ) ,
title = $stanza . find ( 'title' ) . text ( ) ,
instructions = $stanza . find ( 'instructions' ) . text ( ) ,
2014-07-19 12:31:02 +02:00
i , j , options = [ ] , $field , $options ;
2013-06-02 00:21:06 +02:00
var input _types = {
'text-private' : 'password' ,
'text-single' : 'textline' ,
'boolean' : 'checkbox' ,
'hidden' : 'hidden' ,
'list-single' : 'dropdown'
} ;
2013-07-23 18:42:45 +02:00
$form . find ( 'span.spinner' ) . remove ( ) ;
2013-06-02 00:21:06 +02:00
$form . append ( $ ( '<legend>' ) . text ( title ) ) ;
if ( instructions != title ) {
$form . append ( $ ( '<p>' ) . text ( instructions ) ) ;
}
for ( i = 0 ; i < $fields . length ; i ++ ) {
$field = $ ( $fields [ i ] ) ;
if ( $field . attr ( 'type' ) == 'list-single' ) {
options = [ ] ;
$options = $field . find ( 'option' ) ;
for ( j = 0 ; j < $options . length ; j ++ ) {
2014-01-19 05:21:49 +01:00
options . push ( converse . templates . select _option ( {
2013-06-02 00:21:06 +02:00
value : $ ( $options [ j ] ) . find ( 'value' ) . text ( ) ,
label : $ ( $options [ j ] ) . attr ( 'label' )
} ) ) ;
2013-05-31 16:55:58 +02:00
}
2014-03-09 14:56:35 +01:00
$form . append ( converse . templates . form _select ( {
2013-06-02 00:21:06 +02:00
name : $field . attr ( 'var' ) ,
label : $field . attr ( 'label' ) ,
options : options . join ( '' )
} ) ) ;
} else if ( $field . attr ( 'type' ) == 'boolean' ) {
2014-01-19 05:21:49 +01:00
$form . append ( converse . templates . form _checkbox ( {
2013-06-02 00:21:06 +02:00
name : $field . attr ( 'var' ) ,
type : input _types [ $field . attr ( 'type' ) ] ,
label : $field . attr ( 'label' ) || '' ,
checked : $field . find ( 'value' ) . text ( ) === "1" && 'checked="1"' || ''
} ) ) ;
} else {
2014-01-19 05:21:49 +01:00
$form . append ( converse . templates . form _input ( {
2013-06-02 00:21:06 +02:00
name : $field . attr ( 'var' ) ,
type : input _types [ $field . attr ( 'type' ) ] ,
label : $field . attr ( 'label' ) || '' ,
value : $field . find ( 'value' ) . text ( )
} ) ) ;
}
}
$form . append ( '<input type="submit" value="' + _ _ ( 'Save' ) + '"/>' ) ;
$form . append ( '<input type="button" value="' + _ _ ( 'Cancel' ) + '"/>' ) ;
$form . on ( 'submit' , $ . proxy ( this . saveConfiguration , this ) ) ;
$form . find ( 'input[type=button]' ) . on ( 'click' , $ . proxy ( this . cancelConfiguration , this ) ) ;
} ,
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 ( ) {
var $input = $ ( this ) , value ;
if ( $input . is ( '[type=checkbox]' ) ) {
value = $input . is ( ':checked' ) && 1 || 0 ;
} else {
value = $input . val ( ) ;
}
2014-01-19 05:21:49 +01:00
var cnode = $ ( converse . templates . field ( {
2013-06-02 00:21:06 +02:00
name : $input . attr ( 'name' ) ,
value : value
} ) ) [ 0 ] ;
configArray . push ( cnode ) ;
if ( ! -- count ) {
converse . connection . muc . saveConfiguration (
that . model . get ( 'jid' ) ,
configArray ,
$ . proxy ( that . onConfigSaved , that ) ,
$ . proxy ( that . onErrorConfigSaved , that )
) ;
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 ( ) ;
that . $el . find ( '.chat-area' ) . show ( ) ;
that . $el . find ( '.participants' ) . show ( ) ;
} ) ;
} ,
2013-05-13 09:10:47 +02:00
2013-06-02 00:21:06 +02:00
onConfigSaved : function ( stanza ) {
// XXX
} ,
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 ( ) ;
that . $el . find ( '.chat-area' ) . show ( ) ;
that . $el . find ( '.participants' ) . show ( ) ;
} ) ;
} ,
configureChatRoom : function ( ev ) {
ev . preventDefault ( ) ;
if ( this . $el . find ( 'div.chatroom-form-container' ) . length ) {
return ;
}
2014-09-20 22:59:29 +02:00
this . $ ( '.chat-body' ) . children ( ) . hide ( ) ;
this . $ ( '.chat-body' ) . append (
2013-06-02 00:21:06 +02:00
$ ( '<div class="chatroom-form-container">' +
'<form class="chatroom-form">' +
2013-07-23 18:42:45 +02:00
'<span class="spinner centered"/>' +
2013-06-02 00:21:06 +02:00
'</form>' +
'</div>' ) ) ;
converse . connection . muc . configure (
2013-03-24 16:54:29 +01:00
this . model . get ( 'jid' ) ,
2013-06-02 00:21:06 +02:00
$ . proxy ( this . renderConfigurationForm , this )
) ;
} ,
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"/>' ) ;
2013-06-02 00:21:06 +02:00
this . connect ( password ) ;
} ,
renderPasswordForm : function ( ) {
2014-09-20 22:59:29 +02:00
this . $ ( '.chat-body' ) . children ( ) . hide ( ) ;
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
this . $ ( '.chat-body' ) . append (
converse . templates . chatroom _password _form ( {
heading : _ _ ( 'This chatroom requires a password' ) ,
label _password : _ _ ( 'Password: ' ) ,
label _submit : _ _ ( 'Submit' )
} ) ) ;
this . $ ( '.chatroom-form' ) . on ( 'submit' , $ . proxy ( this . submitPassword , this ) ) ;
2013-06-02 00:21:06 +02:00
} ,
showDisconnectMessage : function ( msg ) {
2014-09-20 22:59:29 +02:00
this . $ ( '.chat-area' ) . hide ( ) ;
this . $ ( '.participants' ) . hide ( ) ;
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
this . $ ( '.chat-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
} ,
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
* /
var $chat _content ,
2013-06-02 00:21:06 +02:00
disconnect _msgs = [ ] ,
2014-09-06 12:25:37 +02:00
msgs = [ ] ,
reasons = [ ] ;
$el . find ( 'x[xmlns="' + Strophe . NS . MUC _USER + '"]' ) . each ( $ . proxy ( function ( idx , x ) {
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 ( ) ) ;
}
} ) ;
$ ( x ) . find ( 'status' ) . each ( $ . proxy ( function ( idx , stat ) {
var code = stat . getAttribute ( 'code' ) ;
if ( is _self && _ . contains ( _ . keys ( this . newNicknameMessages ) , code ) ) {
this . model . save ( { 'nick' : Strophe . getResourceFromJid ( $el . attr ( 'from' ) ) } ) ;
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 ) ) {
msgs . push (
_ _ ( this . actionInfoMessages [ code ] , Strophe . unescapeNode ( Strophe . getResourceFromJid ( $el . attr ( 'from' ) ) ) )
) ;
} 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.
}
}
} , this ) ) ;
} , this ) ) ;
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 ) ;
}
2013-06-02 00:21:06 +02:00
this . model . set ( 'connected' , false ) ;
return ;
}
2014-09-06 12:25:37 +02:00
$chat _content = this . $el . find ( '.chat-content' ) ;
for ( i = 0 ; i < msgs . length ; i ++ ) {
$chat _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
}
2013-08-31 15:45:23 +02:00
return this . scrollDown ( ) ;
2013-06-02 00:21:06 +02:00
} ,
showErrorMessage : function ( $error , room ) {
// We didn't enter the room, so we must remove it from the MUC
// add-on
2013-08-15 20:33:42 +02:00
delete converse . connection . muc [ room . name ] ;
2013-06-02 00:21:06 +02:00
if ( $error . attr ( 'type' ) == 'auth' ) {
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' ) ) ;
}
} else if ( $error . attr ( 'type' ) == 'modify' ) {
if ( $error . find ( 'jid-malformed' ) . length ) {
this . showDisconnectMessage ( _ _ ( 'No nickname was specified' ) ) ;
}
} else if ( $error . attr ( 'type' ) == 'cancel' ) {
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" ) ) ;
}
}
} ,
onChatRoomPresence : function ( presence , room ) {
2014-09-06 12:25:37 +02:00
var $presence = $ ( presence ) , is _self ;
2013-06-02 00:21:06 +02:00
if ( $presence . attr ( 'type' ) === 'error' ) {
this . model . set ( 'connected' , false ) ;
this . showErrorMessage ( $presence . find ( 'error' ) , room ) ;
} else {
2014-09-06 12:25:37 +02:00
is _self = ( $presence . find ( "status[code='110']" ) . length ) || ( $presence . attr ( 'from' ) == room . name + '/' + Strophe . escapeNode ( room . nick ) ) ;
2014-09-20 22:59:29 +02:00
if ( ! this . model . get ( 'conneced' ) ) {
this . model . set ( 'connected' , true ) ;
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
this . $el . find ( '.chat-body' ) . children ( ) . show ( ) ;
}
2013-06-02 00:21:06 +02:00
this . showStatusMessages ( $presence , is _self ) ;
}
return true ;
} ,
onChatRoomMessage : function ( message ) {
var $message = $ ( message ) ,
body = $message . children ( 'body' ) . text ( ) ,
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 ) || '' ,
delayed = $message . find ( 'delay' ) . length > 0 ,
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
this . showStatusMessages ( $message ) ;
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!
2014-09-12 22:23:42 +02:00
this . $el . find ( '.chat-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 ;
}
this . model . createMessage ( $message ) ;
if ( ! delayed && sender !== this . model . get ( 'nick' ) && ( new RegExp ( "\\b" + this . model . get ( 'nick' ) + "\\b" ) ) . test ( body ) ) {
2014-09-03 18:47:24 +02:00
playNotification ( ) ;
}
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 ;
} ,
onChatRoomRoster : function ( roster , room ) {
2014-09-12 15:23:21 +02:00
return this . occupantsview . onChatRoomRoster ( roster , room ) ;
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 (
$ . proxy ( function ( message ) {
2014-03-05 00:23:45 +01:00
this . onMessage ( message ) ;
2014-02-11 12:44:27 +01:00
return true ;
} , this ) , null , 'message' , 'chat' ) ;
2014-08-30 16:00:52 +02:00
converse . connection . addHandler (
$ . proxy ( function ( message ) {
this . onInvite ( message ) ;
return true ;
} , 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 ,
success : $ . proxy ( function ( collection , resp ) {
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 } ) ;
2013-06-02 00:21:06 +02:00
} , 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
} ) ;
if ( ! chatroom . get ( 'connected' ) ) {
2014-09-02 20:01:49 +02:00
converse . chatboxviews . get ( room _jid ) . connect ( null ) ;
2014-08-30 16:00:52 +02:00
}
}
} ,
2014-03-05 00:23:45 +01:00
onMessage : function ( message ) {
2014-08-30 16:00:52 +02:00
var $message = $ ( message ) ;
var buddy _jid , $forwarded , $received ,
2014-11-08 15:50:00 +01:00
msgid = $message . attr ( 'id' ) ,
2014-11-08 16:58:42 +01:00
chatbox , resource , roster _item ,
2013-06-02 00:21:06 +02:00
message _from = $message . attr ( 'from' ) ;
2014-04-19 06:58:26 +02:00
if ( message _from === converse . connection . jid ) {
2013-06-02 00:21:06 +02:00
// FIXME: Forwarded messages should be sent to specific resources,
// not broadcasted
return true ;
}
2014-08-30 16:00:52 +02:00
$forwarded = $message . children ( 'forwarded' ) ;
$received = $message . children ( 'received[xmlns="urn:xmpp:carbons:2"]' ) ;
2013-06-02 00:21:06 +02:00
if ( $forwarded . length ) {
$message = $forwarded . children ( 'message' ) ;
2014-08-30 16:00:52 +02:00
} else if ( $received . length ) {
$message = $received . children ( 'forwarded' ) . children ( 'message' ) ;
2014-04-19 06:58:26 +02:00
message _from = $message . attr ( 'from' ) ;
2013-06-02 00:21:06 +02:00
}
var from = Strophe . getBareJidFromJid ( message _from ) ,
2014-11-08 16:58:42 +01:00
to = Strophe . getBareJidFromJid ( $message . attr ( 'to' ) ) ;
2013-06-02 00:21:06 +02:00
if ( from == converse . bare _jid ) {
// I am the sender, so this must be a forwarded message...
2013-11-04 14:57:22 +01:00
buddy _jid = to ;
2013-06-02 00:21:06 +02:00
resource = Strophe . getResourceFromJid ( $message . attr ( 'to' ) ) ;
} else {
2013-11-04 14:57:22 +01:00
buddy _jid = from ;
2013-06-02 00:21:06 +02:00
resource = Strophe . getResourceFromJid ( message _from ) ;
}
2013-10-03 16:05:21 +02:00
2014-11-08 15:50:00 +01:00
roster _item = converse . roster . get ( buddy _jid ) ;
2013-09-08 15:54:04 +02:00
if ( roster _item === undefined ) {
2013-10-03 14:22:33 +02:00
// The buddy was likely removed
2013-11-04 14:57:22 +01:00
converse . log ( 'Could not get roster item for JID ' + buddy _jid , 'error' ) ;
2013-10-03 14:22:33 +02:00
return true ;
2013-09-08 15:54:04 +02:00
}
2013-10-03 14:22:33 +02:00
2014-11-08 16:58:42 +01:00
chatbox = this . get ( buddy _jid ) ;
2013-06-02 00:21:06 +02:00
if ( ! chatbox ) {
2013-11-04 14:57:22 +01:00
var fullname = roster _item . get ( 'fullname' ) ;
fullname = _ . isEmpty ( fullname ) ? buddy _jid : fullname ;
2013-06-02 00:21:06 +02:00
chatbox = this . create ( {
2013-11-04 14:57:22 +01:00
'id' : buddy _jid ,
'jid' : buddy _jid ,
'fullname' : fullname ,
2013-06-02 00:21:06 +02:00
'image_type' : roster _item . get ( 'image_type' ) ,
'image' : roster _item . get ( 'image' ) ,
'url' : roster _item . get ( 'url' )
} ) ;
}
2014-11-08 16:58:42 +01:00
if ( msgid && chatbox . messages . findWhere ( { msgid : msgid } ) ) {
// FIXME: There's still a bug here..
// If a duplicate message is received just after the chat
// box was closed, then it'll open again (due to it being
// created here above), with now new messages.
// The solution is mostly likely to not let chat boxes show
// automatically when they are created, but to require
// "show" to be called explicitly.
return true ; // We already have this message stored.
}
2014-08-20 21:00:28 +02:00
if ( ! this . isOnlyChatStateNotification ( $message ) && from !== converse . bare _jid ) {
2014-09-03 18:47:24 +02:00
playNotification ( ) ;
2014-08-20 21:00:28 +02:00
}
2014-04-19 06:58:26 +02:00
chatbox . receiveMessage ( $message ) ;
2014-08-03 23:02:25 +02:00
converse . roster . addResource ( buddy _jid , resource ) ;
2014-07-06 18:31:17 +02:00
converse . emit ( 'message' , message ) ;
2013-06-02 00:21:06 +02:00
return true ;
}
} ) ;
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 ) {
if ( item . get ( 'minimized' ) === false ) {
this . trimChats ( this . get ( item . get ( 'id' ) ) ) ;
} else {
this . trimChats ( ) ;
}
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
if ( ( minimized _width + boxes _width + controlbox _width ) > this . $el . outerWidth ( true ) ) {
2014-06-14 18:21:52 +02:00
oldest _chat = this . getOldestMaximizedChat ( ) ;
if ( oldest _chat ) {
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 ) {
var i , chatbox ;
// TODO: once Backbone.Overview has been refactored, we should
// be able to call .each on the views themselves.
this . model . each ( $ . proxy ( function ( model ) {
var id = model . get ( 'id' ) ;
if ( include _controlbox || id !== 'controlbox' ) {
this . get ( id ) . close ( ) ;
}
} , this ) ) ;
return this ;
} ,
2014-05-27 18:34:22 +02:00
showChat : function ( attrs ) {
2014-08-31 01:17:52 +02:00
/ * F i n d t h e c h a t b o x a n d s h o w i t .
* If it doesn ' t exist , create it .
* /
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 ) {
if ( ! ( m . get ( 'composing' ) || m . get ( 'paused' ) ) ) {
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 ) {
2014-05-31 21:26:18 +02:00
if ( ev && ev . preventDefault ) {
ev . preventDefault ( ) ;
}
2014-06-14 18:21:52 +02:00
this . remove ( ) ;
2014-06-08 21:43:00 +02:00
this . model . maximize ( ) ;
2014-06-11 22:53:14 +02:00
} , 200 )
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 ;
var attrs = _ . extend ( {
'id' : jid ,
2014-09-02 16:42:08 +02:00
'fullname' : jid ,
'chat_status' : 'offline' ,
2013-06-02 00:21:06 +02:00
'user_id' : Strophe . getNodeFromJid ( jid ) ,
'resources' : [ ] ,
2014-07-29 19:53:57 +02:00
'groups' : [ ] ,
2013-06-02 00:21:06 +02:00
'status' : ''
} , attributes ) ;
this . set ( attrs ) ;
2014-10-26 23:10:43 +01:00
} ,
showInRoster : function ( ) {
return ( ! converse . show _only _online _users || this . get ( 'chat_status' ) === 'online' ) ;
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
openChat : function ( ev ) {
2014-08-01 20:09:32 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2014-10-12 10:34:33 +02:00
// XXX: Can this.model.attributes be used here, instead of
// manually specifying all attributes?
2014-05-27 18:34:22 +02:00
return converse . chatboxviews . showChat ( {
2013-06-02 00:21:06 +02:00
'id' : this . model . get ( 'jid' ) ,
'jid' : this . model . get ( 'jid' ) ,
'fullname' : this . model . get ( 'fullname' ) ,
'image_type' : this . model . get ( 'image_type' ) ,
'image' : this . model . get ( 'image' ) ,
'url' : this . model . get ( 'url' ) ,
'status' : this . model . get ( 'status' )
2013-05-12 13:53:37 +02:00
} ) ;
2013-06-02 00:21:06 +02:00
} ,
2013-05-11 16:19:42 +02:00
2013-06-02 00:21:06 +02:00
removeContact : function ( ev ) {
2014-08-01 20:09:32 +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 remove this contact?" ) ) ;
2013-06-02 00:21:06 +02:00
if ( result === true ) {
var bare _jid = this . model . get ( 'jid' ) ;
2014-07-31 21:50:34 +02:00
converse . connection . roster . remove ( bare _jid , $ . proxy ( function ( iq ) {
2013-06-02 00:21:06 +02:00
converse . connection . roster . unauthorize ( bare _jid ) ;
converse . rosterview . model . remove ( bare _jid ) ;
2014-08-02 15:05:27 +02:00
this . model . destroy ( ) ;
2014-07-31 21:50:34 +02:00
this . remove ( ) ;
} , this ) ) ;
2013-06-02 00:21:06 +02:00
}
} ,
acceptRequest : function ( ev ) {
2014-07-19 14:50:25 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2013-06-02 00:21:06 +02:00
var jid = this . model . get ( 'jid' ) ;
converse . connection . roster . authorize ( jid ) ;
converse . connection . roster . add ( jid , this . model . get ( 'fullname' ) , [ ] , function ( iq ) {
converse . connection . roster . subscribe ( jid , null , converse . xmppstatus . get ( 'fullname' ) ) ;
} ) ;
} ,
declineRequest : function ( ev ) {
2014-07-19 14:50:25 +02:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var result = confirm ( _ _ ( "Are you sure you want to decline this contact request?" ) ) ;
if ( result === true ) {
converse . connection . roster . unauthorize ( this . model . get ( 'jid' ) ) ;
this . model . destroy ( ) ;
}
return this ;
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 ( ) , {
'desc_remove' : _ _ ( 'Click to remove this contact' )
} )
) ) ;
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" ) ,
2014-10-26 15:52:27 +01:00
'desc_decline' : _ _ ( "Click to decline this contact request" )
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' ) ;
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' ) ,
'desc_remove' : _ _ ( 'Click to remove this contact' )
} )
2013-10-05 22:16:09 +02:00
) ) ;
2013-06-02 00:21:06 +02: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 ) {
2013-06-02 00:21:06 +02:00
var $this = $ ( this ) ,
jid = $this . attr ( 'jid' ) ,
action = $this . attr ( 'action' ) ,
fullname = $this . attr ( 'name' ) ;
if ( action === 'add' ) {
2014-10-27 18:48:54 +01:00
converse . connection . roster . subscribe ( 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 ) ) ;
} ,
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 ) {
if ( _ . indexOf ( resources , resource ) == - 1 ) {
resources . push ( resource ) ;
item . set ( { 'resources' : resources } ) ;
}
} else {
item . set ( { 'resources' : [ resource ] } ) ;
}
}
} ,
removeResource : 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 ,
idx ;
if ( item ) {
resources = item . get ( 'resources' ) ;
idx = _ . indexOf ( resources , resource ) ;
if ( idx !== - 1 ) {
resources . splice ( idx , 1 ) ;
item . set ( { 'resources' : resources } ) ;
return resources . length ;
2013-05-30 21:24:00 +02:00
}
2013-06-02 00:21:06 +02:00
}
return 0 ;
} ,
subscribeBack : function ( jid ) {
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
if ( converse . connection . roster . findItem ( bare _jid ) ) {
converse . connection . roster . authorize ( bare _jid ) ;
converse . connection . roster . subscribe ( jid , null , converse . xmppstatus . get ( 'fullname' ) ) ;
2013-05-30 21:24:00 +02:00
} else {
2013-06-02 00:21:06 +02:00
converse . connection . roster . add ( jid , '' , [ ] , function ( iq ) {
converse . connection . roster . authorize ( bare _jid ) ;
converse . connection . roster . subscribe ( jid , null , converse . xmppstatus . get ( 'fullname' ) ) ;
} ) ;
}
} ,
unsubscribe : 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 .
* /
converse . xmppstatus . sendPresence ( 'unsubscribe' ) ;
if ( converse . connection . roster . findItem ( jid ) ) {
converse . connection . roster . remove ( jid , function ( iq ) {
converse . rosterview . model . remove ( jid ) ;
} ) ;
}
} ,
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 ;
} ,
2014-09-15 21:33:44 +02:00
clearCache : function ( items ) {
2013-06-02 00:21:06 +02:00
/ * T h e l o c a l s t o r a g e c a c h e c o n t a i n i n g r o s t e r c o n t a c t s m i g h t c o n t a i n
* some contacts that aren ' t actually in our roster anymore . We
* therefore need to remove them now .
* /
2014-09-15 21:33:44 +02:00
var id , i , contact ;
2013-06-02 00:21:06 +02:00
for ( i = 0 ; i < this . models . length ; ++ i ) {
id = this . models [ i ] . get ( 'id' ) ;
2014-09-15 21:33:44 +02:00
if ( _ . indexOf ( _ . pluck ( items , 'jid' ) , id ) === - 1 ) {
contact = this . get ( id ) ;
2014-10-24 18:55:32 +02:00
if ( contact && ! contact . get ( 'requesting' ) ) {
2014-09-15 21:33:44 +02:00
contact . destroy ( ) ;
}
2013-06-02 00:21:06 +02:00
}
2013-05-30 21:24:00 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
2013-05-30 21:24:00 +02:00
2014-10-24 18:55:32 +02:00
// TODO: see if we can only use 2nd item par
rosterHandler : function ( items , item ) {
2014-07-06 18:31:17 +02:00
converse . emit ( 'roster' , items ) ;
2014-09-15 21:33:44 +02:00
this . clearCache ( items ) ;
2014-10-27 21:41:41 +01:00
var new _items = item ? [ item ] : items ;
_ . each ( new _items , function ( item , index , items ) {
2013-06-02 00:21:06 +02:00
if ( this . isSelf ( item . jid ) ) { return ; }
2013-10-08 08:45:17 +02:00
var model = this . get ( item . jid ) ;
2013-06-02 00:21:06 +02:00
if ( ! model ) {
2013-10-20 12:16:14 +02:00
var is _last = ( index === ( items . length - 1 ) ) ? true : false ;
if ( ( item . subscription === 'none' ) && ( item . ask === null ) && ! is _last ) {
2013-10-19 18:24:06 +02:00
// We're not interested in zombies
2013-10-20 12:16:14 +02:00
// (Hack: except if it's the last item, then we still
// add it so that the roster will be shown).
2013-10-19 18:24:06 +02:00
return ;
}
2013-06-02 00:21:06 +02:00
this . create ( {
ask : item . ask ,
fullname : item . name || item . jid ,
2014-07-14 21:44:18 +02:00
groups : item . groups ,
jid : item . jid ,
subscription : item . subscription
2014-10-25 12:33:24 +02:00
} , { sort : false } ) ;
2013-06-02 00:21:06 +02:00
} else {
if ( ( item . subscription === 'none' ) && ( item . ask === null ) ) {
// This user is no longer in our roster
model . destroy ( ) ;
2014-07-14 21:44:18 +02:00
} else {
// 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
model . save ( {
subscription : item . subscription ,
ask : item . ask ,
requesting : null ,
groups : item . groups
} ) ;
2013-06-02 00:21:06 +02:00
}
}
} , 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
2013-10-05 16:46:57 +02:00
handleIncomingSubscription : function ( jid ) {
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
2013-10-08 08:45:17 +02:00
var item = this . get ( bare _jid ) ;
2013-10-05 16:46:57 +02:00
if ( ! converse . allow _contact _requests ) {
converse . connection . roster . unauthorize ( bare _jid ) ;
return true ;
}
if ( converse . auto _subscribe ) {
if ( ( ! item ) || ( item . get ( 'subscription' ) != 'to' ) ) {
this . subscribeBack ( jid ) ;
} else {
converse . connection . roster . authorize ( bare _jid ) ;
}
} else {
if ( ( item ) && ( item . get ( 'subscription' ) != 'none' ) ) {
converse . connection . roster . authorize ( bare _jid ) ;
} else {
if ( ! this . get ( bare _jid ) ) {
converse . getVCard (
bare _jid ,
$ . proxy ( function ( jid , fullname , img , img _type , url ) {
this . add ( {
jid : bare _jid ,
2013-11-06 08:54:58 +01:00
subscription : 'none' ,
ask : null ,
requesting : true ,
2014-09-15 23:09:04 +02:00
fullname : fullname || jid ,
2013-10-05 16:46:57 +02:00
image : img ,
image _type : img _type ,
url : url ,
2014-08-03 19:42:23 +02:00
vcard _updated : moment ( ) . format ( )
2013-10-05 16:46:57 +02:00
} ) ;
} , this ) ,
2014-09-18 18:51:23 +02:00
$ . proxy ( function ( jid , iq ) {
2013-10-05 16:46:57 +02:00
converse . log ( "Error while retrieving vcard" ) ;
2013-11-06 07:43:55 +01:00
this . add ( {
jid : bare _jid ,
2013-11-06 08:54:58 +01:00
subscription : 'none' ,
ask : null ,
requesting : true ,
2014-09-18 18:51:23 +02:00
fullname : bare _jid ,
vcard _updated : moment ( ) . format ( )
2013-11-06 07:43:55 +01:00
} ) ;
2013-10-05 16:46:57 +02:00
} , this )
) ;
} else {
return true ;
}
}
}
return true ;
} ,
2013-06-02 00:21:06 +02:00
presenceHandler : function ( presence ) {
var $presence = $ ( presence ) ,
presence _type = $presence . attr ( 'type' ) ;
if ( presence _type === 'error' ) {
return true ;
}
var jid = $presence . attr ( 'from' ) ,
bare _jid = Strophe . getBareJidFromJid ( jid ) ,
resource = Strophe . getResourceFromJid ( jid ) ,
$show = $presence . find ( 'show' ) ,
chat _status = $show . text ( ) || 'online' ,
status _message = $presence . find ( 'status' ) ,
2014-08-03 19:42:23 +02:00
contact ;
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' ) ) {
2013-06-02 00:21:06 +02:00
// Another resource has changed it's status, we'll update ours as well.
converse . xmppstatus . save ( { 'status' : chat _status } ) ;
}
2013-05-30 21:24:00 +02:00
return true ;
2013-06-02 00:21:06 +02:00
} else if ( ( $presence . find ( 'x' ) . attr ( 'xmlns' ) || '' ) . indexOf ( Strophe . NS . MUC ) === 0 ) {
return true ; // Ignore MUC
}
2014-08-03 19:42:23 +02:00
contact = this . get ( bare _jid ) ;
if ( contact && ( status _message . text ( ) != contact . get ( 'status' ) ) ) {
contact . save ( { 'status' : status _message . text ( ) } ) ;
2013-05-30 21:24:00 +02:00
}
2013-06-02 00:21:06 +02:00
if ( ( presence _type === 'subscribed' ) || ( presence _type === 'unsubscribe' ) ) {
return true ;
} else if ( presence _type === 'subscribe' ) {
2013-10-05 16:46:57 +02:00
return this . handleIncomingSubscription ( jid ) ;
2013-06-02 00:21:06 +02:00
} else if ( presence _type === 'unsubscribed' ) {
this . unsubscribe ( bare _jid ) ;
} else if ( presence _type === 'unavailable' ) {
if ( this . removeResource ( bare _jid , resource ) === 0 ) {
2014-08-03 19:42:23 +02:00
if ( contact ) {
contact . save ( { 'chat_status' : 'offline' } ) ;
2013-06-02 00:21:06 +02:00
}
2012-09-21 16:04:57 +02:00
}
2014-08-03 19:42:23 +02:00
} else if ( contact ) {
2013-06-02 00:21:06 +02:00
// presence_type is undefined
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
}
2013-06-02 00:21:06 +02:00
return true ;
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 ( ) ; }
if ( this . $el [ 0 ] . style . display === "none" ) { this . $el . show ( ) ; }
} 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 ) ;
2014-08-02 15:05:27 +02:00
} else if ( index == ( this . model . contacts . length - 1 ) ) {
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 ( ) {
2014-10-26 23:10:43 +01:00
// FIXME: There's a bug here, if show_only_online_users is true
// Possible solution, get the group, call _.each and check
2014-10-27 18:48:54 +01:00
// showInRoster
2014-08-31 19:25:54 +02:00
this . $el . nextUntil ( 'dt' ) . addBack ( ) . show ( ) ;
} ,
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 .
* /
var matches , rejects ;
2014-08-08 19:08:47 +02:00
if ( q . length === 0 ) {
if ( this . model . get ( 'state' ) === OPENED ) {
this . model . contacts . each ( $ . proxy ( 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 ( ) ;
}
} , this ) ) ;
}
this . showIfInvisible ( ) ;
} 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 {
_ . each ( matches , $ . proxy ( function ( item ) {
this . get ( item . get ( 'id' ) ) . $el . hide ( ) ;
} , this ) ) ;
2014-08-30 13:26:10 +02:00
_ . each ( this . model . contacts . reject ( contains . not ( 'fullname' , q ) ) , $ . proxy ( function ( item ) {
2014-08-08 21:41:47 +02:00
this . get ( item . get ( 'id' ) ) . $el . show ( ) ;
} , this ) ) ;
2014-08-08 19:08:47 +02:00
this . showIfInvisible ( ) ;
}
}
} ,
showIfInvisible : function ( ) {
if ( ! this . $el . is ( ':visible' ) ) {
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 ( ) {
2014-08-02 15:05:27 +02:00
this . registerRosterHandler ( ) ;
this . registerRosterXHandler ( ) ;
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
} ,
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
} ) ) ;
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.
success : $ . proxy ( function ( collection , resp , options ) {
if ( collection . length !== 0 ) {
this . positionFetchedGroups ( collection , resp , options ) ;
}
converse . roster . fetch ( {
add : true ,
success : function ( collection ) {
// XXX: Bit of a hack.
// strophe.roster expects .get to be called for
// every page load so that its "items" attr
// gets populated.
// This is very inefficient for large rosters,
// and we already have the roster cached in
// sessionStorage.
// Therefore we manually populate the "items"
// attr.
// Ideally we should eventually replace
// strophe.roster with something better.
if ( collection . length > 0 ) {
collection . each ( function ( item ) {
converse . connection . roster . items . push ( {
name : item . get ( 'fullname' ) ,
jid : item . get ( 'jid' ) ,
subscription : item . get ( 'subscription' ) ,
ask : item . get ( 'ask' ) ,
groups : item . get ( 'groups' ) ,
resources : item . get ( 'resources' )
} ) ;
} ) ;
converse . initial _presence _sent = 1 ;
converse . xmppstatus . sendPresence ( ) ;
} else {
converse . connection . roster . get ( ) ;
}
}
} ) ;
} , 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 ) {
var matches ;
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 ( ) ; }
var q = ev . target . value ;
2014-08-11 21:47:51 +02:00
var t = this . $ ( '.filter-type' ) . val ( ) ;
2014-08-08 21:41:47 +02:00
$ ( ev . target ) [ 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 ( ) {
// Register handlers that depend on the roster
converse . connection . roster . registerCallback (
2014-10-27 21:35:06 +01:00
$ . proxy ( converse . roster . rosterHandler , converse . roster )
) ;
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 ( ) ;
$ . proxy ( converse . roster . subscribeToSuggestedItems , converse . roster ) ( msg ) ;
} ,
t
) ;
t += $ ( msg ) . find ( 'item' ) . length * 250 ;
return true ;
} ,
2014-08-02 15:05:27 +02:00
'http://jabber.org/protocol/rosterx' , 'message' , null ) ;
} ,
registerPresenceHandler : function ( ) {
converse . connection . addHandler (
$ . proxy ( function ( presence ) {
2014-08-03 23:02:25 +02:00
converse . roster . presenceHandler ( presence ) ;
2014-08-02 15:05:27 +02:00
return true ;
} , this ) , null , 'presence' , null ) ;
} ,
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' ) ) {
if ( contact . changed . subscription == 'from' ) {
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
} else if ( contact . get ( 'subscription' ) === 'both' ) {
this . addExistingContact ( contact ) ;
}
}
2014-09-15 23:28:07 +02:00
if ( _ . has ( contact . changed , 'ask' ) && contact . changed . ask == 'subscribe' ) {
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
}
if ( _ . has ( contact . changed , 'subscription' ) && contact . changed . requesting == 'true' ) {
this . addContactToGroup ( contact , HEADER _REQUESTING _CONTACTS ) ;
2014-09-15 22:48:04 +02:00
}
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
* 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 .
* /
model . sort ( ) ;
model . each ( $ . proxy ( 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 ) ;
}
} , this ) ) ;
} ,
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 ) ;
2014-08-02 15:05:27 +02: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
}
2014-08-02 15:05:27 +02:00
_ . each ( groups , $ . proxy ( function ( name ) {
2014-08-03 19:42:23 +02:00
this . addContactToGroup ( contact , name ) ;
2014-08-02 15:05:27 +02:00
} , this ) ) ;
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 ( {
2013-08-15 20:47:19 +02:00
'status' : this . get ( 'status' ) || 'online'
2013-06-02 00:21:06 +02:00
} ) ;
2013-12-15 15:58:46 +01:00
this . on ( 'change' , $ . proxy ( 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
$ . proxy ( function ( jid , fullname , image , image _type , url ) {
this . save ( { 'fullname' : fullname } ) ;
} , this )
) ;
}
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
}
2013-08-04 15:39:46 +02:00
} , this ) ) ;
2013-06-02 00:21:06 +02:00
} ,
sendPresence : function ( type ) {
2013-08-04 15:39:46 +02:00
if ( type === undefined ) {
type = this . get ( 'status' ) || 'online' ;
}
2013-06-02 00:21:06 +02:00
var status _message = this . get ( 'status_message' ) ,
presence ;
// 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' ) ) {
presence = $pres ( { 'type' : 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
}
2013-06-02 00:21:06 +02:00
converse . connection . send ( presence ) ;
} ,
setStatus : function ( value ) {
this . sendPresence ( value ) ;
this . save ( { 'status' : value } ) ;
} ,
setStatusMessage : function ( status _message ) {
converse . connection . send ( $pres ( ) . c ( 'show' ) . t ( this . get ( 'status' ) ) . up ( ) . c ( 'status' ) . t ( status _message ) ) ;
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 }
} ) ;
}
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 ( ) {
this . model . on ( "change" , this . updateStatusUI , this ) ;
} ,
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 ,
options _list = [ ] ,
that = this ;
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
2014-10-10 11:05:16 +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' )
} ) ;
2013-06-02 00:21:06 +02:00
this . $el . find ( '.xmpp-status' ) . replaceWith ( input ) ;
this . $el . find ( '.custom-xmpp-status' ) . focus ( ) . focus ( ) ;
} ,
setStatusMessage : function ( ev ) {
ev . preventDefault ( ) ;
2013-07-30 00:16:17 +02:00
var status _message = $ ( ev . target ) . find ( 'input' ) . val ( ) ;
2013-06-02 00:21:06 +02:00
this . model . setStatusMessage ( status _message ) ;
} ,
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 ) {
2014-04-24 09:51:21 +02:00
var pretty _status ;
2013-06-02 00:21:06 +02:00
if ( stat === 'chat' ) {
pretty _status = _ _ ( 'online' ) ;
} else if ( stat === 'dnd' ) {
pretty _status = _ _ ( 'busy' ) ;
} else if ( stat === 'xa' ) {
pretty _status = _ _ ( 'away for long' ) ;
} else if ( stat === 'away' ) {
pretty _status = _ _ ( 'away' ) ;
} else {
2014-02-11 23:41:51 +01:00
pretty _status = _ _ ( stat ) || _ _ ( 'online' ) ;
2013-06-02 00:21:06 +02:00
}
return pretty _status ;
} ,
2012-12-09 20:47:12 +01:00
2013-06-02 00:21:06 +02:00
updateStatusUI : function ( model ) {
if ( ! ( _ . has ( model . changed , 'status' ) ) && ! ( _ . has ( model . changed , 'status_message' ) ) ) {
2013-04-12 21:01:04 +02:00
return ;
}
2013-06-02 00:21:06 +02:00
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 ) ) ;
this . $el . find ( '#fancy-xmpp-status-select' ) . 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
2014-09-06 23:34:39 +02:00
this . BOSHSession = Backbone . Model ;
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 ) ) ;
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
converse . connection . disco . info ( converse . domain , null , $ . proxy ( this . onInfo , this ) ) ;
converse . connection . disco . items ( converse . domain , null , $ . proxy ( this . onItems , this ) ) ;
} else {
this . fetch ( { add : true } ) ;
2013-02-20 17:21:07 +01:00
}
2013-06-02 00:21:06 +02:00
} ,
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
2014-09-06 23:34:39 +02:00
*
* TODO : these features need to be added in the relevant
* feature - providing Models , not here
2014-08-31 14:14:50 +02:00
* /
converse . connection . disco . addFeature ( 'http://jabber.org/protocol/chatstates' ) ; // Limited support
converse . connection . disco . addFeature ( 'http://jabber.org/protocol/rosterx' ) ; // Limited support
converse . connection . disco . addFeature ( 'jabber:x:conference' ) ;
converse . connection . disco . addFeature ( 'urn:xmpp:carbons:2' ) ;
2014-11-08 15:34:12 +01:00
converse . connection . disco . addFeature ( Strophe . NS . VCARD ) ;
2014-08-31 14:14:50 +02:00
converse . connection . disco . addFeature ( Strophe . NS . BOSH ) ;
converse . connection . disco . addFeature ( Strophe . NS . DISCO _INFO ) ;
converse . connection . disco . addFeature ( Strophe . NS . MUC ) ;
return this ;
} ,
2013-06-02 00:21:06 +02:00
onItems : function ( stanza ) {
$ ( stanza ) . find ( 'query item' ) . each ( $ . proxy ( function ( idx , item ) {
converse . connection . disco . info (
$ ( item ) . attr ( 'jid' ) ,
null ,
$ . proxy ( this . onInfo , this ) ) ;
} , this ) ) ;
} ,
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
}
2013-06-02 00:21:06 +02:00
$stanza . find ( 'feature' ) . each ( $ . proxy ( function ( idx , feature ) {
this . create ( {
'var' : $ ( feature ) . attr ( 'var' ) ,
'from' : $stanza . attr ( 'from' )
} ) ;
} , this ) ) ;
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
this . LoginPanel = Backbone . View . extend ( {
tagName : 'div' ,
id : "login-dialog" ,
events : {
'submit form#converse-login' : 'authenticate'
} ,
connect : function ( $form , jid , password ) {
if ( $form ) {
2013-08-24 02:20:00 +02:00
$form . find ( 'input[type=submit]' ) . hide ( ) . after ( '<span class="spinner login-submit"/>' ) ;
2013-02-24 10:31:47 +01:00
}
2014-07-14 23:09:39 +02:00
var resource = Strophe . getResourceFromJid ( jid ) ;
if ( ! resource ) {
jid += '/converse.js-' + Math . floor ( Math . random ( ) * 139749825 ) . toString ( ) ;
}
2013-08-24 02:20:00 +02:00
converse . connection . connect ( jid , password , converse . onConnect ) ;
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 ( {
'label_username' : _ _ ( 'XMPP/Jabber Username:' ) ,
'label_password' : _ _ ( 'Password:' ) ,
'label_login' : _ _ ( 'Log In' )
} )
) ) ;
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 ( ) ;
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 ( ) ; }
2013-06-02 00:21:06 +02:00
var $form = $ ( ev . target ) ,
2013-09-02 11:48:09 +02:00
$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 ( ) ,
$bsu _input = null ,
errors = false ;
if ( ! converse . bosh _service _url ) {
$bsu _input = $form . find ( 'input#bosh_service_url' ) ;
converse . bosh _service _url = $bsu _input . val ( ) ;
if ( ! converse . bosh _service _url ) {
errors = true ;
$bsu _input . addClass ( 'error' ) ;
}
2013-03-06 10:42:53 +01:00
}
2013-06-02 00:21:06 +02:00
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
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' ,
height : this . default _box _height ,
closed : ! this . show _controlbox _by _default
} ) ;
} ;
2014-09-06 23:34:39 +02:00
this . initConnection = function ( ) {
var rid , sid , jid ;
2014-10-19 20:41:16 +02:00
if ( this . connection && this . connection . connected ) {
2014-09-06 23:34:39 +02:00
this . onConnected ( ) ;
} else {
// XXX: it's not yet clear what the order of preference should
// be between RID and SID received via the initialize method or
// those received from sessionStorage.
//
// What do you we if we receive values from both avenues?
//
// Also, what do we do when the keepalive session values are
// expired? Do we try to fall back?
if ( ! this . bosh _service _url ) {
throw ( "Error: you must supply a value for the bosh_service_url" ) ;
}
this . connection = new Strophe . Connection ( this . bosh _service _url ) ;
2014-09-17 22:07:58 +02:00
if ( this . prebind ) {
2014-09-30 12:24:12 +02:00
if ( this . jid && this . sid && this . rid ) {
this . connection . attach ( this . jid , this . sid , this . rid , this . onConnect ) ;
}
if ( ! this . keepalive ) {
throw ( "If you use prebind and don't use keepalive, " +
"then you MUST supply JID, RID and SID values" ) ;
2014-09-17 22:07:58 +02:00
}
}
2014-09-06 23:34:39 +02:00
if ( this . keepalive ) {
rid = this . session . get ( 'rid' ) ;
sid = this . session . get ( 'sid' ) ;
jid = this . session . get ( 'jid' ) ;
if ( rid && jid && sid ) {
this . session . save ( { rid : rid } ) ; // The RID needs to be increased with each request.
this . connection . attach ( jid , sid , rid , this . onConnect ) ;
2014-10-06 20:23:59 +02:00
} else if ( this . prebind ) {
2014-09-26 17:20:52 +02:00
delete this . connection ;
this . emit ( 'noResumeableSession' ) ;
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 ;
this . roster . off ( ) . reset ( ) ; // Removes roster contacts
this . connection . roster . _callbacks = [ ] ; // Remove all Roster handlers (e.g. rosterHandler)
this . rosterview . model . off ( ) . reset ( ) ; // Removes roster groups
this . rosterview . undelegateEvents ( ) . remove ( ) ;
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
} ;
2014-10-12 16:24:57 +02:00
this . _initializePlugins = function ( ) {
_ . each ( this . plugins , $ . proxy ( function ( plugin ) {
2014-10-13 22:02:55 +02:00
$ . proxy ( plugin , this ) ( this ) ;
2014-10-12 16:24:57 +02:00
} , this ) ) ;
} ;
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-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 ) {
return {
'endOTR' : $ . proxy ( chatbox . endOTR , chatbox ) ,
'get' : $ . proxy ( chatbox . get , chatbox ) ,
'initiateOTR' : $ . proxy ( chatbox . initiateOTR , chatbox ) ,
'maximize' : $ . proxy ( chatbox . maximize , chatbox ) ,
'minimize' : $ . proxy ( chatbox . minimize , chatbox ) ,
2014-11-09 13:03:56 +01:00
'set' : $ . proxy ( chatbox . set , chatbox ) ,
'open' : chatbox . trigger . bind ( chatbox , 'show' )
2014-10-12 11:11:28 +02:00
} ;
} ;
2013-08-05 09:25:29 +02:00
return {
2014-11-09 13:03:56 +01:00
'initialize' : function ( settings , callback ) {
converse . initialize ( settings , callback ) ;
} ,
'contacts' : {
'get' : function ( jids ) {
var _transform = function ( jid ) {
var contact = converse . roster . get ( Strophe . getBareJidFromJid ( jid ) ) ;
if ( contact ) {
return contact . attributes ;
}
return null ;
} ;
if ( typeof jids === "string" ) {
return _transform ( jids ) ;
}
return _ . map ( jids , _transform ) ;
2014-09-22 15:03:57 +02:00
}
} ,
2014-11-09 13:03:56 +01:00
'chats' : {
'get' : function ( jids ) {
var _transform = function ( jid ) {
var chatbox = converse . chatboxes . get ( jid ) ;
if ( ! chatbox ) {
var roster _item = converse . roster . get ( jid ) ;
if ( roster _item === undefined ) {
converse . log ( 'Could not get roster item for JID ' + jid , 'error' ) ;
return null ;
}
chatbox = converse . chatboxes . create ( {
'id' : jid ,
'jid' : 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 wrappedChatBox ( chatbox ) ;
} ;
if ( typeof jids === "string" ) {
return _transform ( jids ) ;
}
return _ . map ( jids , _transform ) ;
2014-10-12 10:34:33 +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 ) ;
} ,
} ,
'plugins' : {
'add' : function ( name , callback ) {
converse . plugins [ name ] = callback ;
} ,
'remove' : function ( name ) {
delete converse . plugins [ name ] ;
} ,
'override' : function ( obj , attributes ) {
/ * 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
*
* 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
* /
if ( ! obj . prototype . _super ) {
obj . prototype . _super = { } ;
}
_ . each ( attributes , function ( value , key ) {
if ( key === 'events' ) {
obj . prototype [ key ] = _ . extend ( value , obj . prototype [ key ] ) ;
} else {
if ( typeof key === 'function' ) {
obj . prototype . _super [ key ] = obj . prototype [ key ] ;
}
obj . prototype [ key ] = value ;
}
} ) ;
2014-02-12 11:37:39 +01:00
}
} ,
2014-11-09 13:03:56 +01:00
'env' : {
'jQuery' : $ ,
'Strophe' : Strophe ,
'_' : _
} ,
// Deprecated API methods
'getBuddy' : function ( jid ) {
converse . log ( 'WARNING: the "getBuddy" API method has been deprecated. Please use "contacts.get" instead' ) ;
return this . contacts . get ( jid ) ;
} ,
'getChatBox' : function ( jid ) {
converse . log ( 'WARNING: the "getChatBox" API method has been deprecated. Please use "chats.get" instead' ) ;
return this . chats . get ( jid ) ;
2014-10-12 11:11:28 +02:00
} ,
2014-10-12 10:34:33 +02:00
'openChatBox' : function ( jid ) {
2014-11-09 13:03:56 +01:00
converse . log ( 'WARNING: the "openChatBox" API method has been deprecated. Please use "chats.get(jid).open()" instead' ) ;
var chat = this . chats . get ( jid ) ;
if ( chat ) { chat . open ( ) ; }
return chat ;
} ,
'getRID' : function ( ) {
converse . log ( 'WARNING: the "getRID" API method has been deprecated. Please use "tokens.get(\'rid\')" instead' ) ;
return this . tokens . get ( 'rid' ) ;
} ,
'getSID' : function ( ) {
converse . log ( 'WARNING: the "getSID" API method has been deprecated. Please use "tokens.get(\'sid\')" instead' ) ;
return this . tokens . get ( 'sid' ) ;
2014-10-12 10:34:33 +02:00
} ,
2014-10-10 11:05:16 +02:00
'once' : function ( evt , handler ) {
2014-11-09 13:03:56 +01:00
converse . log ( 'WARNING: the "one" API method has been deprecated. Please use "listen.once" instead' ) ;
return this . listen . once ( evt , handler ) ;
2013-12-15 15:58:46 +01:00
} ,
2014-10-10 11:05:16 +02:00
'on' : function ( evt , handler ) {
2014-11-09 13:03:56 +01:00
converse . log ( 'WARNING: the "on" API method has been deprecated. Please use "listen.on" instead' ) ;
return this . listen . on ( evt , handler ) ;
2013-12-15 15:58:46 +01:00
} ,
2014-10-10 11:05:16 +02:00
'off' : function ( evt , handler ) {
2014-11-09 13:03:56 +01:00
converse . log ( 'WARNING: the "off" API method has been deprecated. Please use "listen.not" instead' ) ;
return this . listen . not ( evt , handler ) ;
2014-10-26 15:52:27 +01:00
}
2013-08-05 09:25:29 +02:00
} ;
2012-09-21 16:04:57 +02:00
} ) ) ;