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 >
2012-07-22 16:44:01 +02:00
* Dual licensed under the MIT and GPL Licenses
* /
2012-09-21 16:04:57 +02:00
// AMD/global registrations
( function ( root , factory ) {
2012-12-11 12:45:25 +01:00
if ( console === undefined || console . log === undefined ) {
console = { log : function ( ) { } , error : function ( ) { } } ;
}
2013-02-20 17:21:07 +01:00
if ( typeof define === 'function' && define . amd ) {
2013-03-21 09:54:41 +01:00
define ( "converse" , [
2013-10-03 10:18:07 +02:00
"otr" ,
2013-08-16 15:48:23 +02:00
"crypto.aes" ,
2013-06-02 00:21:06 +02:00
"locales" ,
2013-08-26 14:37:35 +02:00
"backbone.localStorage" ,
2013-08-26 14:45:24 +02:00
"jquery.tinysort" ,
2013-07-30 22:28:54 +02:00
"strophe" ,
2013-03-21 09:54:41 +01:00
"strophe.muc" ,
"strophe.roster" ,
2013-04-25 23:51:55 +02:00
"strophe.vcard" ,
"strophe.disco"
2013-08-16 15:48:23 +02:00
] , function ( otr , crypto ) {
2013-03-27 19:18:00 +01:00
// Use Mustache style syntax for variable interpolation
_ . templateSettings = {
evaluate : /\{\[([\s\S]+?)\]\}/g ,
interpolate : /\{\{([\s\S]+?)\}\}/g
} ;
2013-08-16 15:48:23 +02:00
return factory ( jQuery , _ , crypto , otr , console ) ;
2012-09-21 16:04:57 +02:00
}
) ;
2013-02-20 17:21:07 +01:00
} else {
2012-09-21 16:04:57 +02:00
// Browser globals
2012-09-21 16:42:03 +02:00
_ . templateSettings = {
evaluate : /\{\[([\s\S]+?)\]\}/g ,
interpolate : /\{\{([\s\S]+?)\}\}/g
} ;
2013-08-16 15:48:23 +02:00
root . converse = factory ( jQuery , _ , crypto , otr , console || { log : function ( ) { } } ) ;
2012-09-21 16:04:57 +02:00
}
2013-08-16 15:48:23 +02:00
} ( this , function ( $ , _ , crypto , otr , console ) {
2013-04-20 10:41:10 +02:00
var converse = { } ;
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
} ;
2013-09-08 15:54:04 +02:00
2013-10-05 22:34:47 +02:00
// Default configuration values
// ----------------------------
2013-10-03 14:22:33 +02:00
this . allow _contact _requests = 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 ;
this . auto _subscribe = false ;
2013-08-24 02:43:41 +02:00
this . bosh _service _url = undefined ; // The BOSH connection manager URL.
2013-08-15 19:34:40 +02:00
this . debug = 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 ;
2013-06-02 00:21:06 +02:00
this . prebind = 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 ;
2013-08-24 03:10:06 +02:00
this . testing = false ; // Exposes sensitive data for testing. Never set to true in production systems!
2013-09-02 11:30:54 +02:00
this . xhr _custom _status = false ;
this . xhr _user _search = false ;
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' ,
2013-10-03 13:53:32 +02:00
'allow_muc' ,
'allow_otr' ,
2013-08-24 02:43:41 +02:00
'animate' ,
'auto_list_rooms' ,
'auto_subscribe' ,
'bosh_service_url' ,
2013-09-02 11:30:54 +02:00
'connection' ,
2013-08-24 02:43:41 +02:00
'debug' ,
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' ,
2013-08-24 02:43:41 +02:00
'prebind' ,
2013-09-02 11:30:54 +02:00
'rid' ,
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' ,
2013-09-02 11:30:54 +02:00
'testing' ,
'xhr_custom_status' ,
'xhr_user_search'
2013-10-05 22:34:47 +02:00
] ) ) ;
2013-02-21 13:42:30 +01:00
2013-10-05 22:34:47 +02:00
// Translation machinery
// ---------------------
2013-08-05 09:25:29 +02:00
var _ _ = $ . proxy ( function ( str ) {
2013-09-08 15:54:04 +02:00
// Translation factory
2013-08-28 00:47:14 +02:00
if ( this . i18n === undefined ) {
2013-09-08 15:54:04 +02:00
this . i18n = locales . en ;
2013-08-28 00:47:14 +02:00
}
2013-08-05 09:25:29 +02:00
var t = this . i18n . translate ( str ) ;
2013-06-02 00:21:06 +02:00
if ( arguments . length > 1 ) {
return t . fetch . apply ( t , [ ] . slice . call ( arguments , 1 ) ) ;
} else {
return t . fetch ( ) ;
2013-02-21 13:42:30 +01:00
}
2013-08-05 09:25:29 +02:00
} , this ) ;
2013-08-26 16:21:32 +02:00
var _ _ _ = function ( str ) {
/ * X X X : T h i s i s p a r t o f a h a c k t o g e t g e t t e x t t o s c a n s t r i n g s t o b e
* translated . Strings we cannot send to the function above because
* they require variable interpolation and we don ' t yet have the
* variables at scan time .
*
* See actionInfoMessages
* /
return str ;
} ;
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' )
} ;
// 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-06-02 00:21:06 +02:00
this . autoLink = function ( text ) {
// Convert URLs into hyperlinks
var re = /((http|https|ftp):\/\/[\w?=&.\/\-;#~%\-]+(?![\w\s?&.\/;#~%"=\-]*>))/g ;
return text . replace ( re , '<a target="_blank" href="$1">$1</a>' ) ;
} ;
2013-02-21 13:42:30 +01:00
2013-08-23 00:48:53 +02:00
this . giveFeedback = function ( message , klass ) {
$ ( '.conn-feedback' ) . text ( message ) ;
$ ( '.conn-feedback' ) . attr ( 'class' , 'conn-feedback' ) ;
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 ) {
converse . connection . vcard . get (
$ . proxy ( function ( iq ) {
// Successful callback
$vcard = $ ( iq ) . find ( 'vCard' ) ;
var fullname = $vcard . find ( 'FN' ) . text ( ) ,
img = $vcard . find ( 'BINVAL' ) . text ( ) ,
img _type = $vcard . find ( 'TYPE' ) . text ( ) ,
url = $vcard . find ( 'URL' ) . text ( ) ;
if ( jid ) {
var rosteritem = converse . roster . get ( jid ) ;
if ( rosteritem ) {
rosteritem . save ( {
'fullname' : fullname || jid ,
'image_type' : img _type ,
'image' : img ,
'url' : url ,
'vcard_updated' : converse . toISOString ( new Date ( ) )
} ) ;
}
}
if ( callback ) {
callback ( jid , fullname , img , img _type , url ) ;
}
} , this ) ,
jid ,
function ( iq ) {
// Error callback
var rosteritem = converse . roster . get ( jid ) ;
if ( rosteritem ) {
rosteritem . save ( {
'vcard_updated' : converse . toISOString ( new Date ( ) )
} ) ;
}
if ( errback ) {
errback ( iq ) ;
}
} ) ;
} ;
2013-08-23 00:48:53 +02:00
this . onConnect = function ( status ) {
2013-10-07 09:08:11 +02:00
var $button , $form ;
2013-08-23 00:48:53 +02:00
if ( status === Strophe . Status . CONNECTED ) {
converse . log ( 'Connected' ) ;
converse . onConnected ( ) ;
} else if ( status === Strophe . Status . DISCONNECTED ) {
2013-10-07 09:08:11 +02:00
$form = $ ( '#converse-login' ) ;
$button = $form . find ( 'input[type=submit]' ) ;
2013-08-23 00:48:53 +02:00
if ( $button ) { $button . show ( ) . siblings ( 'span' ) . remove ( ) ; }
converse . giveFeedback ( _ _ ( 'Disconnected' ) , 'error' ) ;
2013-10-07 21:35:52 +02:00
converse . connection . connect (
converse . connection . jid ,
converse . connection . pass ,
converse . onConnect
) ;
2013-08-23 00:48:53 +02:00
} else if ( status === Strophe . Status . Error ) {
2013-10-07 09:08:11 +02:00
$form = $ ( '#converse-login' ) ;
$button = $form . find ( 'input[type=submit]' ) ;
2013-08-23 00:48:53 +02:00
if ( $button ) { $button . show ( ) . siblings ( 'span' ) . remove ( ) ; }
converse . giveFeedback ( _ _ ( 'Error' ) , 'error' ) ;
} else if ( status === Strophe . Status . CONNECTING ) {
converse . giveFeedback ( _ _ ( 'Connecting' ) ) ;
} else if ( status === Strophe . Status . CONNFAIL ) {
2013-08-24 16:11:08 +02:00
converse . chatboxesview . views . controlbox . trigger ( 'connection-fail' ) ;
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 ) {
2013-08-24 16:11:08 +02:00
converse . chatboxesview . views . controlbox . trigger ( 'auth-fail' ) ;
2013-08-23 00:48:53 +02:00
converse . giveFeedback ( _ _ ( 'Authentication Failed' ) , 'error' ) ;
} else if ( status === Strophe . Status . DISCONNECTING ) {
converse . giveFeedback ( _ _ ( 'Disconnecting' ) , 'error' ) ;
} else if ( status === Strophe . Status . ATTACHED ) {
converse . log ( 'Attached' ) ;
converse . onConnected ( ) ;
}
} ;
2013-06-02 00:21:06 +02:00
this . toISOString = function ( date ) {
var pad ;
if ( typeof date . toISOString !== 'undefined' ) {
return date . toISOString ( ) ;
2012-12-06 11:49:03 +01:00
} else {
2013-06-02 00:21:06 +02:00
// IE <= 8 Doesn't have toISOStringMethod
pad = function ( num ) {
return ( num < 10 ) ? '0' + num : '' + num ;
} ;
return date . getUTCFullYear ( ) + '-' +
pad ( date . getUTCMonth ( ) + 1 ) + '-' +
pad ( date . getUTCDate ( ) ) + 'T' +
pad ( date . getUTCHours ( ) ) + ':' +
pad ( date . getUTCMinutes ( ) ) + ':' +
pad ( date . getUTCSeconds ( ) ) + '.000Z' ;
2012-12-06 11:49:03 +01:00
}
2013-06-02 00:21:06 +02:00
} ;
2012-11-20 14:08:29 +01:00
2013-06-02 00:21:06 +02:00
this . parseISO8601 = function ( datestr ) {
/ * P a r s e s s t r i n g f o r m a t t e d a s 2 0 1 3 - 0 2 - 1 4 T 1 1 : 2 7 : 0 8 . 2 6 8 Z t o a D a t e o b j .
* /
var numericKeys = [ 1 , 4 , 5 , 6 , 7 , 10 , 11 ] ,
struct = /^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}\.?\d*)Z\s*$/ . exec ( datestr ) ,
minutesOffset = 0 ,
i , k ;
for ( i = 0 ; ( k = numericKeys [ i ] ) ; ++ i ) {
struct [ k ] = + struct [ k ] || 0 ;
}
// allow undefined days and months
struct [ 2 ] = ( + struct [ 2 ] || 1 ) - 1 ;
struct [ 3 ] = + struct [ 3 ] || 1 ;
if ( struct [ 8 ] !== 'Z' && struct [ 9 ] !== undefined ) {
minutesOffset = struct [ 10 ] * 60 + struct [ 11 ] ;
if ( struct [ 9 ] === '+' ) {
minutesOffset = 0 - minutesOffset ;
}
}
return new Date ( Date . UTC ( struct [ 1 ] , struct [ 2 ] , struct [ 3 ] , struct [ 4 ] , struct [ 5 ] + minutesOffset , struct [ 6 ] , struct [ 7 ] ) ) ;
} ;
2013-03-24 20:45:55 +01:00
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 . showControlBox = function ( ) {
var controlbox = this . chatboxes . get ( 'controlbox' ) ;
if ( ! controlbox ) {
this . chatboxes . add ( {
id : 'controlbox' ,
box _id : 'controlbox' ,
visible : true
} ) ;
if ( this . connection ) {
this . chatboxes . get ( 'controlbox' ) . save ( ) ;
}
} else {
controlbox . trigger ( 'show' ) ;
}
2013-06-02 00:21:06 +02:00
} ;
2012-06-24 13:16:19 +02:00
2013-10-05 22:34:47 +02:00
this . toggleControlBox = function ( ) {
if ( $ ( "div#controlbox" ) . is ( ':visible' ) ) {
var controlbox = this . chatboxes . get ( 'controlbox' ) ;
if ( this . connection ) {
controlbox . destroy ( ) ;
} else {
controlbox . trigger ( 'hide' ) ;
}
} else {
this . showControlBox ( ) ;
}
2013-06-02 00:21:06 +02:00
} ;
2013-03-21 13:11:45 +01:00
2013-10-05 22:34:47 +02:00
this . initStatus = function ( callback ) {
this . xmppstatus = new this . XMPPStatus ( ) ;
var id = hex _sha1 ( 'converse.xmppstatus-' + this . bare _jid ) ;
this . xmppstatus . id = id ; // This appears to be necessary for backbone.localStorage
this . xmppstatus . localStorage = new Backbone . LocalStorage ( id ) ;
this . xmppstatus . fetch ( { success : callback , error : callback } ) ;
2013-06-02 00:21:06 +02:00
} ;
2013-03-21 13:11:45 +01:00
2013-10-05 22:34:47 +02:00
this . initRoster = function ( ) {
// Set up the roster
this . roster = new this . RosterItems ( ) ;
this . roster . localStorage = new Backbone . LocalStorage (
hex _sha1 ( 'converse.rosteritems-' + converse . bare _jid ) ) ;
// Register callbacks that depend on the roster
this . connection . roster . registerCallback (
$ . proxy ( this . roster . rosterHandler , this . roster ) ,
null , 'presence' , null ) ;
this . connection . addHandler (
$ . proxy ( this . roster . subscribeToSuggestedItems , this . roster ) ,
'http://jabber.org/protocol/rosterx' , 'message' , null ) ;
this . connection . addHandler (
$ . proxy ( function ( presence ) {
this . presenceHandler ( presence ) ;
return true ;
} , this . roster ) , null , 'presence' , null ) ;
// No create the view which will fetch roster items from
// localStorage
this . rosterview = new this . RosterView ( { 'model' : this . roster } ) ;
} ;
this . onConnected = function ( ) {
if ( this . debug ) {
this . connection . xmlInput = function ( body ) { console . log ( body ) ; } ;
this . connection . xmlOutput = function ( body ) { console . log ( body ) ; } ;
Strophe . log = function ( level , msg ) { console . log ( level + ' ' + msg ) ; } ;
2013-10-07 21:35:52 +02:00
Strophe . error = function ( msg ) { console . log ( 'ERROR: ' + msg ) ; } ;
2013-10-05 22:34:47 +02:00
}
this . bare _jid = Strophe . getBareJidFromJid ( this . connection . jid ) ;
this . domain = Strophe . getDomainFromJid ( this . connection . jid ) ;
this . features = new this . Features ( ) ;
this . initStatus ( $ . proxy ( function ( ) {
this . initRoster ( ) ;
this . chatboxes . onConnected ( ) ;
this . connection . roster . get ( function ( ) { } ) ;
$ ( window ) . on ( "blur focus" , $ . proxy ( function ( e ) {
if ( ( this . windowState != e . type ) && ( e . type == 'focus' ) ) {
converse . clearMsgCounter ( ) ;
}
this . windowState = e . type ;
} , this ) ) ;
this . giveFeedback ( _ _ ( 'Online Contacts' ) ) ;
if ( this . testing ) {
this . callback ( this ) ;
} else {
this . callback ( ) ;
}
} , this ) ) ;
} ;
// Backbone Models and Views
// -------------------------
2013-06-02 00:21:06 +02:00
this . Message = Backbone . Model . extend ( ) ;
this . Messages = Backbone . Collection . extend ( {
model : converse . Message
} ) ;
this . ChatBox = Backbone . Model . extend ( {
initialize : function ( ) {
if ( this . get ( 'box_id' ) !== 'controlbox' ) {
2013-08-31 22:28:33 +02:00
if ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . get ( 'otr_status' ) ) ) {
this . initiateOTR ( ) ;
}
2013-06-02 00:21:06 +02:00
this . messages = new converse . Messages ( ) ;
this . messages . localStorage = new Backbone . LocalStorage (
2013-09-12 15:29:34 +02:00
hex _sha1 ( 'converse.messages' + this . get ( 'jid' ) + converse . bare _jid ) ) ;
2013-06-02 00:21:06 +02:00
this . set ( {
'user_id' : Strophe . getNodeFromJid ( this . get ( 'jid' ) ) ,
'box_id' : hex _sha1 ( this . get ( 'jid' ) ) ,
2013-08-31 22:28:33 +02:00
'otr_status' : this . get ( 'otr_status' ) || UNENCRYPTED
2013-03-21 13:11:45 +01:00
} ) ;
}
2013-06-02 00:21:06 +02:00
} ,
2013-09-08 18:13:49 +02:00
getSession : function ( ) {
2013-09-10 22:37:12 +02:00
// XXX: sessionStorage is not supported in IE < 8. Perhaps a
// user alert is required here...
var saved _key = window . sessionStorage [ hex _sha1 ( this . id + 'priv_key' ) ] ;
var instance _tag = window . sessionStorage [ hex _sha1 ( this . id + 'instance_tag' ) ] ;
2013-08-31 20:44:43 +02:00
var cipher = crypto . lib . PasswordBasedCipher ;
var pass = converse . connection . pass ;
2013-09-08 18:13:49 +02:00
var result , key ;
2013-09-10 22:37:12 +02:00
if ( saved _key && instance _tag ) {
2013-09-01 21:37:22 +02:00
var decrypted = cipher . decrypt ( crypto . algo . AES , saved _key , pass ) ;
2013-09-08 18:13:49 +02:00
key = otr . DSA . parsePrivate ( decrypted . toString ( crypto . enc . Latin1 ) ) ;
2013-09-01 21:37:22 +02:00
if ( cipher . decrypt ( crypto . algo . AES , this . get ( 'pass_check' ) , pass ) . toString ( crypto . enc . Latin1 ) === 'match' ) {
2013-08-31 20:44:43 +02:00
// Verified that the user's password is still the same
2013-09-10 22:37:12 +02:00
this . trigger ( 'showHelpMessages' , [ _ _ ( 'Re-establishing encrypted session' ) ] ) ;
2013-09-08 18:13:49 +02:00
return {
'key' : key ,
2013-09-10 22:37:12 +02:00
'instance_tag' : instance _tag
2013-09-08 18:13:49 +02:00
} ;
2013-08-31 20:44:43 +02:00
}
2013-09-08 18:13:49 +02:00
}
2013-09-10 22:37:12 +02:00
// We need to generate a new key and instance tag
2013-09-10 22:52:15 +02:00
result = alert ( _ _ ( 'Your browser needs to generate a private key, which will be used in your encrypted chat session. This can take up to 30 seconds during which your browser might freeze and become unresponsive.' ) ) ;
2013-09-10 22:37:12 +02:00
instance _tag = otr . OTR . makeInstanceTag ( ) ;
2013-09-08 18:13:49 +02:00
key = new otr . DSA ( ) ;
2013-09-10 22:37:12 +02:00
// Encrypt the key and set in sessionStorage. Also store
// instance tag
window . sessionStorage [ hex _sha1 ( this . id + 'priv_key' ) ] =
cipher . encrypt ( crypto . algo . AES , key . packPrivate ( ) , pass ) . toString ( ) ;
window . sessionStorage [ hex _sha1 ( this . id + 'instance_tag' ) ] = instance _tag ;
2013-09-08 18:13:49 +02:00
this . trigger ( 'showHelpMessages' , [ _ _ ( 'Private key generated.' ) ] ) ;
2013-09-10 22:37:12 +02:00
this . save ( { 'pass_check' : cipher . encrypt ( crypto . algo . AES , 'match' , pass ) . toString ( ) } ) ;
2013-09-08 18:13:49 +02:00
return {
'key' : key ,
'instance_tag' : instance _tag
} ;
2013-08-16 15:48:23 +02:00
} ,
2013-08-31 22:28:33 +02:00
updateOTRStatus : function ( state ) {
switch ( state ) {
case otr . OTR . CONST . STATUS _AKE _SUCCESS :
if ( this . otr . msgstate === otr . OTR . CONST . MSGSTATE _ENCRYPTED ) {
this . save ( { 'otr_status' : UNVERIFIED } ) ;
}
break ;
case otr . OTR . CONST . STATUS _END _OTR :
if ( this . otr . msgstate === otr . OTR . CONST . MSGSTATE _FINISHED ) {
this . save ( { 'otr_status' : FINISHED } ) ;
} else if ( this . otr . msgstate === otr . OTR . CONST . MSGSTATE _PLAINTEXT ) {
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' :
if ( this . otr . trust === true ) {
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 } ) ;
2013-09-08 18:13:49 +02:00
session = this . getSession ( ) ;
this . otr = new otr . 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
2013-09-08 18:13:49 +02: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 ) ;
2013-08-16 16:05:24 +02:00
} , this ) ) ;
2013-09-08 18:13:49 +02:00
if ( query _msg ) {
this . otr . receiveMsg ( query _msg ) ;
} else {
this . otr . sendQueryMsg ( ) ;
}
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 } ) ;
} ,
2013-08-24 23:09:54 +02:00
createMessage : function ( message ) {
2013-06-02 00:21:06 +02:00
var $message = $ ( message ) ,
body = converse . autoLink ( $message . children ( 'body' ) . text ( ) ) ,
from = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ,
composing = $message . find ( 'composing' ) ,
delayed = $message . find ( 'delay' ) . length > 0 ,
fullname = ( this . get ( 'fullname' ) || '' ) . split ( ' ' ) [ 0 ] ,
stamp , time , sender ;
if ( ! body ) {
if ( composing . length ) {
this . messages . add ( {
fullname : fullname ,
sender : 'them' ,
delayed : delayed ,
time : converse . toISOString ( new Date ( ) ) ,
composing : composing . length
} ) ;
}
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 {
time = converse . toISOString ( new Date ( ) ) ;
}
if ( from == converse . bare _jid ) {
sender = 'me' ;
} else {
sender = 'them' ;
}
this . messages . create ( {
fullname : fullname ,
sender : sender ,
delayed : delayed ,
time : time ,
message : body
} ) ;
2013-03-21 13:11:45 +01:00
}
2013-08-24 23:09:54 +02:00
} ,
messageReceived : function ( message ) {
var $body = $ ( message ) . children ( 'body' ) ;
var text = ( $body . length > 0 ? converse . autoLink ( $body . text ( ) ) : undefined ) ;
2013-09-08 15:54:04 +02:00
if ( ( ! text ) || ( ! converse . allow _otr ) ) {
return this . createMessage ( message ) ;
}
if ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . get ( 'otr_status' ) ) ) {
this . otr . receiveMsg ( text ) ;
} else {
if ( text . match ( /^\?OTR/ ) ) {
// They want to initiate OTR
if ( ! this . otr ) {
this . initiateOTR ( text ) ;
2013-08-24 23:09:54 +02:00
} else {
2013-09-08 15:54:04 +02:00
this . otr . receiveMsg ( text ) ;
2013-08-24 23:09:54 +02:00
}
2013-09-08 15:54:04 +02:00
} else {
// Normal unencrypted message.
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' ,
events : {
'click .close-chatbox-button' : 'closeChat' ,
2013-08-25 22:10:32 +02:00
'keypress textarea.chat-textarea' : 'keyPressed' ,
'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' ,
'click .auth-otr' : 'authOTR'
2013-06-02 00:21:06 +02:00
} ,
2013-08-25 22:10:32 +02:00
template : _ . template (
'<div class="chat-head chat-head-chatbox">' +
'<a class="close-chatbox-button icon-close"></a>' +
'<a href="{{url}}" target="_blank" class="user">' +
'<div class="chat-title"> {{ fullname }} </div>' +
'</a>' +
'<p class="user-custom-message"><p/>' +
'</div>' +
'<div class="chat-content"></div>' +
'<form class="sendXMPPMessage" action="" method="post">' +
2013-09-08 16:55:40 +02:00
'{[ if (' + converse . show _toolbar + ') { ]}' +
'<ul class="chat-toolbar no-text-select"></ul>' +
'{[ } ]}' +
2013-08-25 22:10:32 +02:00
'<textarea ' +
'type="text" ' +
'class="chat-textarea" ' +
'placeholder="' + _ _ ( 'Personal message' ) + '"/>' +
2013-09-08 15:54:04 +02:00
'</form>'
) ,
2013-08-25 22:10:32 +02:00
2013-08-30 22:49:33 +02:00
toolbar _template : _ . template (
2013-09-08 15:54:04 +02:00
'{[ if (allow_otr) { ]}' +
'<li class="toggle-otr {{otr_status_class}}" title="{{otr_tooltip}}">' +
'<span class="chat-toolbar-text">{{otr_translated_status}}</span>' +
'{[ if (otr_status == "' + UNENCRYPTED + '") { ]}' +
'<span class="icon-unlocked"></span>' +
2013-08-31 15:00:04 +02:00
'{[ } ]}' +
2013-09-08 15:54:04 +02:00
'{[ if (otr_status == "' + UNVERIFIED + '") { ]}' +
'<span class="icon-lock"></span>' +
2013-08-31 15:00:04 +02:00
'{[ } ]}' +
2013-09-08 15:54:04 +02:00
'{[ if (otr_status == "' + VERIFIED + '") { ]}' +
'<span class="icon-lock"></span>' +
2013-08-31 15:00:04 +02:00
'{[ } ]}' +
2013-09-08 15:54:04 +02:00
'{[ if (otr_status == "' + FINISHED + '") { ]}' +
'<span class="icon-unlocked"></span>' +
'{[ } ]}' +
'<ul>' +
'{[ if (otr_status == "' + UNENCRYPTED + '") { ]}' +
'<li><a class="start-otr" href="#">' + _ _ ( 'Start encrypted conversation' ) + '</a></li>' +
'{[ } ]}' +
'{[ if (otr_status != "' + UNENCRYPTED + '") { ]}' +
'<li><a class="start-otr" href="#">' + _ _ ( 'Refresh encrypted conversation' ) + '</a></li>' +
'<li><a class="end-otr" href="#">' + _ _ ( 'End encrypted conversation' ) + '</a></li>' +
'<li><a class="auth-otr" data-scheme="smp" href="#">' + _ _ ( 'Verify with SMP' ) + '</a></li>' +
'{[ } ]}' +
'{[ if (otr_status == "' + UNVERIFIED + '") { ]}' +
'<li><a class="auth-otr" data-scheme="fingerprint" href="#">' + _ _ ( 'Verify with fingerprints' ) + '</a></li>' +
'{[ } ]}' +
'<li><a href="http://www.cypherpunks.ca/otr/help/3.2.0/levels.php" target="_blank">' + _ _ ( "What\'s this?" ) + '</a></li>' +
'</ul>' +
'</li>' +
'{[ } ]}'
) ,
2013-08-30 22:49:33 +02:00
2013-06-02 00:21:06 +02:00
message _template : _ . template (
2013-08-30 22:49:33 +02:00
'<div class="chat-message {{extra_classes}}">' +
'<span class="chat-message-{{sender}}">{{time}} {{username}}: </span>' +
'<span class="chat-message-content">{{message}}</span>' +
'</div>' ) ,
2013-06-02 00:21:06 +02:00
action _template : _ . template (
2013-08-30 22:49:33 +02:00
'<div class="chat-message {{extra_classes}}">' +
'<span class="chat-message-{{sender}}">{{time}} **{{username}} </span>' +
'<span class="chat-message-content">{{message}}</span>' +
'</div>' ) ,
2013-06-02 00:21:06 +02:00
new _day _template : _ . template (
2013-08-30 22:49:33 +02:00
'<time class="chat-date" datetime="{{isodate}}">{{datestring}}</time>'
) ,
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 ) {
2013-08-25 12:06:53 +02:00
this . showOTRMessage ( text , 'me' ) ;
} , this ) ;
2013-08-25 12:17:46 +02:00
this . model . on ( 'showReceivedOTRMessage' , function ( text ) {
2013-08-25 12:06:53 +02:00
this . showOTRMessage ( text , 'them' ) ;
} , this ) ;
2013-08-16 16:05:24 +02:00
this . updateVCard ( ) ;
this . $el . appendTo ( converse . chatboxesview . $el ) ;
this . render ( ) . show ( ) . model . messages . fetch ( { add : true } ) ;
if ( this . model . get ( 'status' ) ) {
this . showStatusMessage ( this . model . get ( 'status' ) ) ;
}
} ,
2013-08-30 22:49:33 +02:00
render : function ( ) {
this . $el . attr ( 'id' , this . model . get ( 'box_id' ) )
. html ( this . template ( this . model . toJSON ( ) ) ) ;
this . renderToolbar ( ) . renderAvatar ( ) ;
return this ;
} ,
2013-08-25 12:06:53 +02:00
showStatusNotification : function ( message , replace ) {
var $chat _content = this . $el . find ( '.chat-content' ) ;
$chat _content . find ( 'div.chat-event' ) . remove ( ) . end ( )
. append ( $ ( '<div class="chat-event"></div>' ) . text ( message ) ) ;
this . scrollDown ( ) ;
} ,
showMessage : function ( $el , msg _dict ) {
2013-06-02 00:21:06 +02:00
var this _date = converse . parseISO8601 ( msg _dict . time ) ,
text = msg _dict . message ,
match = text . match ( /^\/(.*?)(?: (.*))?$/ ) ,
sender = msg _dict . sender ,
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/ , '' ) ;
template = this . action _template ;
username = msg _dict . fullname ;
} else {
template = this . message _template ;
2013-09-10 23:31:29 +02:00
username = sender === 'me' && _ _ ( 'me' ) || msg _dict . fullname ;
2013-06-02 00:21:06 +02:00
}
$el . find ( 'div.chat-event' ) . remove ( ) ;
$el . append (
template ( {
'sender' : sender ,
2013-07-24 23:10:56 +02:00
'time' : this _date . toTimeString ( ) . substring ( 0 , 5 ) ,
2013-06-02 00:21:06 +02:00
'message' : text ,
'username' : username ,
'extra_classes' : msg _dict . delayed && 'delayed' || ''
2013-03-27 16:06:09 +01:00
} ) ) ;
2013-08-31 15:45:23 +02:00
return this . scrollDown ( ) ;
2013-06-02 00:21:06 +02:00
} ,
2013-08-24 23:09:54 +02:00
showOTRMessage : function ( text , sender ) {
2013-08-25 12:17:46 +02:00
/ * " O f f - t h e - r e c o r d " m e s s a g e s a r e e n c r y p t e d a n d n o t s t o r e d a t a l l ,
* so we don ' t have a backbone converse . Message object to work with .
* /
2013-08-24 23:09:54 +02:00
var username = sender === 'me' && sender || this . model . get ( 'fullname' ) ;
var $el = this . $el . find ( '.chat-content' ) ;
$el . find ( 'div.chat-event' ) . remove ( ) ;
$el . append (
this . message _template ( {
'sender' : sender ,
'time' : ( new Date ( ) ) . toTimeString ( ) . substring ( 0 , 5 ) ,
'message' : text ,
'username' : username ,
'extra_classes' : ''
} ) ) ;
2013-08-31 15:45:23 +02:00
return this . scrollDown ( ) ;
2013-08-24 23:09:54 +02:00
} ,
2013-08-31 15:00:04 +02:00
showHelpMessages : function ( msgs , type ) {
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
}
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' ) ,
this _date = converse . parseISO8601 ( time ) ,
$chat _content = this . $el . find ( '.chat-content' ) ,
previous _message , idx , prev _date , isodate , text , match ;
// 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 ) ;
prev _date = converse . parseISO8601 ( previous _message . get ( 'time' ) ) ;
isodate = new Date ( this _date . getTime ( ) ) ;
isodate . setUTCHours ( 0 , 0 , 0 , 0 ) ;
isodate = converse . toISOString ( isodate ) ;
if ( this . isDifferentDay ( prev _date , this _date ) ) {
$chat _content . append ( this . new _day _template ( {
isodate : isodate ,
datestring : this _date . toString ( ) . substring ( 0 , 15 )
} ) ) ;
}
2012-12-06 11:49:03 +01:00
}
2013-06-02 00:21:06 +02:00
if ( message . get ( 'composing' ) ) {
2013-08-25 12:06:53 +02:00
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + 'is typing' ) ;
2012-12-06 14:33:43 +01:00
return ;
2013-06-02 00:21:06 +02:00
} else {
2013-08-25 12:06:53 +02:00
this . showMessage ( $chat _content , _ . 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-06-02 00:21:06 +02:00
isDifferentDay : function ( prev _date , next _date ) {
return (
( next _date . getDate ( ) != prev _date . getDate ( ) ) ||
( next _date . getFullYear ( ) != prev _date . getFullYear ( ) ) ||
( next _date . getMonth ( ) != prev _date . getMonth ( ) ) ) ;
} ,
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' } ) ;
// Forward the message, so that other connected resources are also aware of it.
// TODO: Forward the message only to other connected resources (inside the browser)
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 ( message ) ;
converse . connection . send ( forwarded ) ;
} ,
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" ) {
this . $el . find ( '.chat-content' ) . empty ( ) ;
2013-09-13 09:57:14 +02:00
this . model . messages . reset ( ) . localStorage . _clear ( ) ;
2013-06-02 00:21:06 +02:00
return ;
}
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 ;
2013-09-08 15:54:04 +02:00
} else if ( ( converse . allow _otr ) || ( match [ 1 ] === "endotr" ) ) {
return this . endOTR ( ) ;
2013-09-08 16:55:40 +02:00
} else if ( ( converse . allow _otr ) || ( match [ 1 ] === "otr" ) ) {
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.
this . model . messages . create ( {
fullname : converse . xmppstatus . get ( 'fullname' ) || converse . bare _jid ,
sender : 'me' ,
time : converse . toISOString ( new Date ( ) ) ,
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 ) ;
}
}
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
} ,
2013-08-25 22:10:32 +02:00
toggleOTRMenu : function ( ev ) {
ev . stopPropagation ( ) ;
$ ( ev . currentTarget ) . children ( 'ul' ) . slideToggle ( 200 , function ( ) {
$ ( document ) . click ( function ( ) {
if ( $ ( '.toggle-otr ul' ) . is ( ':visible' ) ) {
$ ( '.toggle-otr ul' , this ) . slideUp ( ) ;
}
} ) ;
} ) ;
} ,
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-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' ) {
2013-09-10 22:52:15 +02: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 have been 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-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' ) ;
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
}
2013-08-30 22:49:33 +02:00
}
if ( _ . has ( item . changed , 'status' ) ) {
2013-06-02 00:21:06 +02:00
this . showStatusMessage ( item . get ( 'status' ) ) ;
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
}
2013-06-02 00:21:06 +02:00
// TODO check for changed fullname as well
} ,
showStatusMessage : function ( msg ) {
this . $el . find ( 'p.user-custom-message' ) . text ( msg ) . attr ( 'title' , msg ) ;
} ,
closeChat : function ( ) {
if ( converse . connection ) {
this . model . destroy ( ) ;
} else {
this . model . trigger ( 'hide' ) ;
}
} ,
updateVCard : function ( ) {
var jid = this . model . get ( 'jid' ) ,
rosteritem = converse . roster . get ( jid ) ;
2013-10-03 14:22:33 +02:00
if ( ( rosteritem ) && ( ! rosteritem . 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." ) ) ;
}
return this . showHelpMessages ( msgs ) ;
} ,
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' ) ;
}
data . allow _otr = converse . allow _otr ;
data . otr _translated _status = OTR _TRANSLATED _MAPPING [ data . otr _status ] ;
data . otr _status _class = OTR _CLASS _MAPPING [ data . otr _status ] ;
this . $el . find ( '.chat-toolbar' ) . html ( this . toolbar _template ( data ) ) ;
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' ) ,
2013-08-25 13:50:04 +02:00
canvas = $ ( '<canvas height="33px" width="33px" class="avatar"></canvas>' ) ,
2013-06-02 00:21:06 +02:00
ctx = canvas . get ( 0 ) . getContext ( '2d' ) ,
img = new Image ( ) ; // Create new Image object
img . onload = function ( ) {
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 ( ) ;
return this ;
} ,
2013-03-25 12:08:27 +01:00
2013-06-02 00:21:06 +02:00
hide : function ( ) {
if ( converse . animate ) {
this . $el . hide ( 'fast' ) ;
} else {
this . $el . hide ( ) ;
}
} ,
show : function ( ) {
if ( this . $el . is ( ':visible' ) && this . $el . css ( 'opacity' ) == "1" ) {
return this . focus ( ) ;
}
if ( converse . animate ) {
this . $el . css ( { 'opacity' : 0 , 'display' : 'inline' } ) . animate ( { opacity : '1' } , 200 ) ;
} else {
this . $el . css ( { 'opacity' : 1 , 'display' : 'inline' } ) ;
}
if ( converse . connection ) {
// Without a connection, we haven't yet initialized
// localstorage
this . model . save ( ) ;
}
return this ;
} ,
scrollDown : function ( ) {
var $content = this . $el . find ( '.chat-content' ) ;
$content . scrollTop ( $content [ 0 ] . scrollHeight ) ;
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' ,
className : 'oc-chat-content' ,
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'
} ,
tab _template : _ . template ( '<li><a class="s current" href="#users">' + _ _ ( 'Contacts' ) + '</a></li>' ) ,
template : _ . template (
'<form class="set-xmpp-status" action="" method="post">' +
'<span id="xmpp-status-holder">' +
'<select id="select-xmpp-status" style="display:none">' +
'<option value="online">' + _ _ ( 'Online' ) + '</option>' +
'<option value="dnd">' + _ _ ( 'Busy' ) + '</option>' +
'<option value="away">' + _ _ ( 'Away' ) + '</option>' +
'<option value="offline">' + _ _ ( 'Offline' ) + '</option>' +
'</select>' +
'</span>' +
2013-10-03 14:22:33 +02:00
'</form>'
) ,
add _contact _dropdown _template : _ . template (
2013-06-02 00:21:06 +02:00
'<dl class="add-converse-contact dropdown">' +
'<dt id="xmpp-contact-search" class="fancy-dropdown">' +
'<a class="toggle-xmpp-contact-form" href="#"' +
2013-08-15 20:14:00 +02:00
'title="' + _ _ ( 'Click to add new chat contacts' ) + '">' +
'<span class="icon-plus"></span>' + _ _ ( 'Add a contact' ) + '</a>' +
2013-06-02 00:21:06 +02:00
'</dt>' +
'<dd class="search-xmpp" style="display:none"><ul></ul></dd>' +
'</dl>'
) ,
2013-10-03 14:22:33 +02:00
add _contact _form _template : _ . template (
2013-06-02 00:21:06 +02:00
'<li>' +
'<form class="add-xmpp-contact">' +
'<input type="text" name="identifier" class="username" placeholder="' + _ _ ( 'Contact username' ) + '"/>' +
'<button type="submit">' + _ _ ( 'Add' ) + '</button>' +
'</form>' +
'<li>'
) ,
search _contact _template : _ . template (
'<li>' +
'<form class="search-xmpp-contact">' +
'<input type="text" name="identifier" class="username" placeholder="' + _ _ ( 'Contact name' ) + '"/>' +
'<button type="submit">' + _ _ ( 'Search' ) + '</button>' +
'</form>' +
'<li>'
) ,
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-10-03 14:22:33 +02:00
var widgets = this . template ( ) ;
2013-08-02 12:26:16 +02:00
this . $tabs . append ( this . tab _template ( ) ) ;
2013-06-02 00:21:06 +02:00
if ( converse . xhr _user _search ) {
markup = this . search _contact _template ( ) ;
} else {
2013-10-03 14:22:33 +02:00
markup = this . add _contact _form _template ( ) ;
2013-06-02 00:21:06 +02:00
}
2013-10-03 14:22:33 +02:00
if ( converse . allow _contact _requests ) {
widgets += this . add _contact _dropdown _template ( ) ;
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 ) ;
this . $el . append ( converse . rosterview . $el ) ;
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 ( ) ;
$ . getJSON ( portal _url + "/search-users?q=" + $ ( ev . target ) . find ( 'input.username' ) . val ( ) , function ( data ) {
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-05-31 16:55:58 +02:00
converse . getVCard (
jid ,
$ . proxy ( function ( jid , fullname , image , image _type , url ) {
2013-06-02 00:21:06 +02:00
this . addContact ( jid , fullname ) ;
2013-05-31 16:55:58 +02:00
} , this ) ,
$ . proxy ( function ( stanza ) {
2013-08-22 21:43:34 +02:00
converse . log ( "An error occured while fetching vcard" ) ;
2013-08-15 20:47:19 +02:00
var jid = $ ( stanza ) . attr ( 'from' ) ;
this . addContact ( jid , jid ) ;
2013-06-02 00:21:06 +02:00
} , this ) ) ;
$ ( '.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 ) {
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'
} ,
room _template : _ . template (
'<dd class="available-chatroom">' +
'<a class="open-room" data-room-jid="{{jid}}"' +
2013-08-15 20:36:06 +02:00
'title="' + _ _ ( 'Click to open this room' ) + '" href="#">{{name}}</a>' +
'<a class="room-info icon-room-info" data-room-jid="{{jid}}"' +
'title="' + _ _ ( 'Show more information on this room' ) + '" href="#"> </a>' +
2013-06-02 00:21:06 +02:00
'</dd>' ) ,
2013-08-12 21:32:00 +02:00
// FIXME: check markup in mockup
2013-06-02 00:21:06 +02:00
room _description _template : _ . template (
'<div class="room-info">' +
'<p class="room-info"><strong>' + _ _ ( 'Description:' ) + '</strong> {{desc}}</p>' +
'<p class="room-info"><strong>' + _ _ ( 'Occupants:' ) + '</strong> {{occ}}</p>' +
'<p class="room-info"><strong>' + _ _ ( 'Features:' ) + '</strong> <ul>' +
'{[ if (passwordprotected) { ]}' +
'<li class="room-info locked">' + _ _ ( 'Requires authentication' ) + '</li>' +
'{[ } ]}' +
'{[ if (hidden) { ]}' +
'<li class="room-info">' + _ _ ( 'Hidden' ) + '</li>' +
'{[ } ]}' +
'{[ if (membersonly) { ]}' +
'<li class="room-info">' + _ _ ( 'Requires an invitation' ) + '</li>' +
'{[ } ]}' +
'{[ if (moderated) { ]}' +
'<li class="room-info">' + _ _ ( 'Moderated' ) + '</li>' +
'{[ } ]}' +
'{[ if (nonanonymous) { ]}' +
'<li class="room-info">' + _ _ ( 'Non-anonymous' ) + '</li>' +
'{[ } ]}' +
'{[ if (open) { ]}' +
'<li class="room-info">' + _ _ ( 'Open room' ) + '</li>' +
'{[ } ]}' +
'{[ if (persistent) { ]}' +
'<li class="room-info">' + _ _ ( 'Permanent room' ) + '</li>' +
'{[ } ]}' +
'{[ if (publicroom) { ]}' +
'<li class="room-info">' + _ _ ( 'Public' ) + '</li>' +
'{[ } ]}' +
'{[ if (semianonymous) { ]}' +
'<li class="room-info">' + _ _ ( 'Semi-anonymous' ) + '</li>' +
'{[ } ]}' +
'{[ if (temporary) { ]}' +
'<li class="room-info">' + _ _ ( 'Temporary room' ) + '</li>' +
'{[ } ]}' +
'{[ if (unmoderated) { ]}' +
'<li class="room-info">' + _ _ ( 'Unmoderated' ) + '</li>' +
'{[ } ]}' +
'</p>' +
'</div>'
) ,
tab _template : _ . template ( '<li><a class="s" href="#chatrooms">' + _ _ ( 'Rooms' ) + '</a></li>' ) ,
template : _ . template (
'<form class="add-chatroom" action="" method="post">' +
'<input type="text" name="chatroom" class="new-chatroom-name" placeholder="' + _ _ ( 'Room name' ) + '"/>' +
'<input type="text" name="nick" class="new-chatroom-nick" placeholder="' + _ _ ( 'Nickname' ) + '"/>' +
'<input type="{{ server_input_type }}" name="server" class="new-chatroom-server" placeholder="' + _ _ ( 'Server' ) + '"/>' +
'<input type="submit" name="join" value="' + _ _ ( 'Join' ) + '"/>' +
'<input type="button" name="show" id="show-rooms" value="' + _ _ ( 'Show rooms' ) + '"/>' +
2013-05-20 19:54:22 +02:00
'</form>' +
2013-06-02 00:21:06 +02:00
'<dl id="available-chatrooms"></dl>' ) ,
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 (
this . template ( {
server _input _type : converse . hide _muc _server && 'hidden' || 'text'
} )
) . 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 ( ) {
this . $tabs . append ( this . tab _template ( ) ) ;
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' ) ;
fragment . appendChild ( $ ( this . room _template ( {
'name' : name ,
'jid' : jid
} ) ) [ 0 ] ) ;
}
$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 (
this . room _description _template ( {
'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 ,
'unmoderated' : $stanza . find ( 'feature[var="muc_unmoderated"]' ) . length
} ) ) ;
} , 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 ; }
2013-08-22 21:43:34 +02:00
chatroom = converse . chatboxesview . showChatBox ( {
2013-06-02 00:21:06 +02:00
'id' : jid ,
'jid' : jid ,
'name' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( jid ) ) ,
'nick' : nick ,
'chatroom' : true ,
'box_id' : hex _sha1 ( jid )
} ) ;
if ( ! chatroom . get ( 'connected' ) ) {
converse . chatboxesview . views [ jid ] . connect ( null ) ;
2012-07-15 13:10:11 +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 : {
'click a.close-chatbox-button' : 'closeChat' ,
'click ul#controlbox-tabs li a' : 'switchTab'
} ,
initialize : function ( ) {
this . $el . appendTo ( converse . chatboxesview . $el ) ;
this . model . on ( 'change' , $ . proxy ( function ( item , changed ) {
var i ;
if ( _ . has ( item . changed , 'connected' ) ) {
this . render ( ) ;
converse . features . on ( 'add' , $ . proxy ( 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 ) ;
}
}
if ( _ . has ( item . changed , 'visible' ) ) {
if ( item . changed . visible === true ) {
this . show ( ) ;
}
2013-04-26 14:30:14 +02:00
}
2013-06-02 00:21:06 +02:00
} , this ) ) ;
this . model . on ( 'show' , this . show , this ) ;
this . model . on ( 'destroy' , this . hide , this ) ;
this . model . on ( 'hide' , this . hide , this ) ;
if ( this . model . get ( 'visible' ) ) {
this . show ( ) ;
2013-03-22 16:43:00 +01:00
}
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
} ,
template : _ . template (
'<div class="chat-head oc-chat-head">' +
'<ul id="controlbox-tabs"></ul>' +
2013-08-15 20:14:00 +02:00
'<a class="close-chatbox-button icon-close"></a>' +
2013-06-02 00:21:06 +02:00
'</div>' +
2013-08-31 14:29:58 +02:00
'<div class="controlbox-panes"></div>'
2013-06-02 00:21:06 +02:00
) ,
switchTab : function ( ev ) {
ev . preventDefault ( ) ;
var $tab = $ ( ev . target ) ,
$sibling = $tab . parent ( ) . siblings ( 'li' ) . children ( 'a' ) ,
$tab _panel = $ ( $tab . attr ( 'href' ) ) ,
$sibling _panel = $ ( $sibling . attr ( 'href' ) ) ;
$sibling _panel . fadeOut ( 'fast' , function ( ) {
$sibling . removeClass ( 'current' ) ;
$tab . addClass ( 'current' ) ;
$tab _panel . fadeIn ( 'fast' , function ( ) {
} ) ;
} ) ;
} ,
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 ;
} ,
render : function ( ) {
if ( ( ! converse . prebind ) && ( ! converse . connection ) ) {
// Add login panel if the user still has to authenticate
2013-08-02 12:26:16 +02:00
this . $el . html ( this . template ( this . model . toJSON ( ) ) ) ;
2013-08-31 14:29:58 +02:00
this . loginpanel = new converse . LoginPanel ( { '$parent' : this . $el . find ( '.controlbox-panes' ) , 'model' : this } ) ;
2013-06-02 00:21:06 +02:00
this . loginpanel . render ( ) ;
2013-08-02 12:26:16 +02:00
} else if ( ! this . contactspanel ) {
this . $el . html ( this . template ( this . model . toJSON ( ) ) ) ;
2013-08-31 14:29:58 +02:00
this . contactspanel = new converse . ContactsPanel ( { '$parent' : this . $el . find ( '.controlbox-panes' ) } ) ;
2013-06-02 00:21:06 +02:00
this . contactspanel . render ( ) ;
converse . xmppstatusview = new converse . XMPPStatusView ( { 'model' : converse . xmppstatus } ) ;
converse . xmppstatusview . render ( ) ;
2013-10-03 13:16:26 +02:00
if ( converse . allow _muc ) {
this . roomspanel = new converse . RoomsPanel ( { '$parent' : this . $el . find ( '.controlbox-panes' ) } ) ;
this . roomspanel . render ( ) ;
}
2013-06-02 00:21:06 +02:00
}
return this ;
2013-04-20 11:32:54 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2013-03-22 16:43:00 +01:00
2013-06-02 00:21:06 +02:00
this . ChatRoomView = converse . ChatBoxView . extend ( {
length : 300 ,
tagName : 'div' ,
className : 'chatroom' ,
events : {
'click a.close-chatbox-button' : 'closeChat' ,
'click a.configure-chatroom-button' : 'configureChatRoom' ,
'keypress textarea.chat-textarea' : 'keyPressed'
} ,
info _template : _ . template ( '<div class="chat-info">{{message}}</div>' ) ,
sendChatRoomMessage : function ( body ) {
var match = body . replace ( /^\s*/ , "" ) . match ( /^\/(.*?)(?: (.*))?$/ ) || [ false ] ,
$chat _content ;
switch ( match [ 1 ] ) {
case 'msg' :
// TODO: Private messages
break ;
case 'clear' :
this . $el . find ( '.chat-content' ) . empty ( ) ;
break ;
case 'topic' :
converse . connection . muc . setTopic ( this . model . get ( 'jid' ) , match [ 2 ] ) ;
break ;
case 'kick' :
converse . connection . muc . kick ( this . model . get ( 'jid' ) , match [ 2 ] ) ;
break ;
case 'ban' :
converse . connection . muc . ban ( this . model . get ( 'jid' ) , match [ 2 ] ) ;
break ;
case 'op' :
converse . connection . muc . op ( this . model . get ( 'jid' ) , match [ 2 ] ) ;
break ;
case 'deop' :
converse . connection . muc . deop ( this . model . get ( 'jid' ) , match [ 2 ] ) ;
break ;
case 'help' :
$chat _content = this . $el . find ( '.chat-content' ) ;
msgs = [
'<strong>/help</strong>:' + _ _ ( 'Show this menu' ) + '' ,
'<strong>/me</strong>:' + _ _ ( 'Write in the third person' ) + '' ,
'<strong>/topic</strong>:' + _ _ ( 'Set chatroom topic' ) + '' ,
'<strong>/kick</strong>:' + _ _ ( 'Kick user from chatroom' ) + '' ,
'<strong>/ban</strong>:' + _ _ ( 'Ban user from chatroom' ) + '' ,
'<strong>/clear</strong>:' + _ _ ( 'Remove messages' ) + ''
] ;
2013-08-25 12:06:53 +02:00
this . showHelpMessages ( msgs ) ;
2013-06-02 00:21:06 +02:00
break ;
default :
this . last _msgid = converse . connection . muc . groupchat ( this . model . get ( 'jid' ) , body ) ;
break ;
2013-04-26 14:30:14 +02:00
}
2013-06-02 00:21:06 +02:00
} ,
template : _ . template (
'<div class="chat-head chat-head-chatroom">' +
2013-08-15 20:14:00 +02:00
'<a class="close-chatbox-button icon-close"></a>' +
2013-08-15 20:36:06 +02:00
'<a class="configure-chatroom-button icon-wrench" style="display:none"></a>' +
2013-06-02 00:21:06 +02:00
'<div class="chat-title"> {{ name }} </div>' +
'<p class="chatroom-topic"><p/>' +
'</div>' +
'<div class="chat-body">' +
2013-07-23 18:42:45 +02:00
'<span class="spinner centered"/>' +
2013-06-02 00:21:06 +02:00
'</div>' ) ,
chatarea _template : _ . template (
'<div class="chat-area">' +
'<div class="chat-content"></div>' +
'<form class="sendXMPPMessage" action="" method="post">' +
'<textarea type="text" class="chat-textarea" ' +
'placeholder="' + _ _ ( 'Message' ) + '"/>' +
'</form>' +
'</div>' +
'<div class="participants">' +
'<ul class="participant-list"></ul>' +
'</div>'
) ,
render : function ( ) {
this . $el . attr ( 'id' , this . model . get ( 'box_id' ) )
. html ( this . template ( this . model . toJSON ( ) ) ) ;
return this ;
} ,
renderChatArea : function ( ) {
if ( ! this . $el . find ( '.chat-area' ) . length ) {
this . $el . find ( '.chat-body' ) . empty ( ) . append ( this . chatarea _template ( ) ) ;
2013-05-09 15:39:27 +02:00
}
2013-06-02 00:21:06 +02:00
return this ;
} ,
connect : function ( password ) {
if ( _ . has ( converse . connection . muc . rooms , this . model . get ( 'jid' ) ) ) {
// If the room exists, it already has event listeners, so we
// doing add them again.
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 ) ;
}
} ,
initialize : function ( ) {
this . connect ( null ) ;
2013-08-25 12:06:53 +02:00
this . model . messages . on ( 'add' , this . onMessageAdded , this ) ;
2013-06-02 00:21:06 +02:00
this . model . on ( 'destroy' , function ( model , response , options ) {
this . $el . hide ( 'fast' ) ;
converse . connection . muc . leave (
this . model . get ( 'jid' ) ,
this . model . get ( 'nick' ) ,
$ . proxy ( this . onLeave , this ) ,
undefined ) ;
} ,
this ) ;
this . $el . appendTo ( converse . chatboxesview . $el ) ;
this . render ( ) . show ( ) . model . messages . fetch ( { add : true } ) ;
} ,
onLeave : function ( ) {
this . model . set ( 'connected' , false ) ;
} ,
form _input _template : _ . template ( '<label>{{label}}<input name="{{name}}" type="{{type}}" value="{{value}}"></label>' ) ,
select _option _template : _ . template ( '<option value="{{value}}">{{label}}</option>' ) ,
form _select _template : _ . template ( '<label>{{label}}<select name="{{name}}">{{options}}</select></label>' ) ,
form _checkbox _template : _ . template ( '<label>{{label}}<input name="{{name}}" type="{{type}}" {{checked}}"></label>' ) ,
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 ( ) ,
i , j , options = [ ] ;
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 ++ ) {
options . push ( this . select _option _template ( {
value : $ ( $options [ j ] ) . find ( 'value' ) . text ( ) ,
label : $ ( $options [ j ] ) . attr ( 'label' )
} ) ) ;
2013-05-31 16:55:58 +02:00
}
2013-06-02 00:21:06 +02:00
$form . append ( this . form _select _template ( {
name : $field . attr ( 'var' ) ,
label : $field . attr ( 'label' ) ,
options : options . join ( '' )
} ) ) ;
} else if ( $field . attr ( 'type' ) == 'boolean' ) {
$form . append ( this . form _checkbox _template ( {
name : $field . attr ( 'var' ) ,
type : input _types [ $field . attr ( 'type' ) ] ,
label : $field . attr ( 'label' ) || '' ,
checked : $field . find ( 'value' ) . text ( ) === "1" && 'checked="1"' || ''
} ) ) ;
} else {
$form . append ( this . form _input _template ( {
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 ) ) ;
} ,
field _template : _ . template ( '<field var="{{name}}"><value>{{value}}</value></field>' ) ,
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 ( ) ;
}
var cnode = $ ( that . field _template ( {
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 ;
}
this . $el . find ( '.chat-area' ) . hide ( ) ;
this . $el . find ( '.participants' ) . hide ( ) ;
this . $el . find ( '.chat-body' ) . append (
$ ( '<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 ( ) ;
this . $el . find ( '.chatroom-form-container' ) . replaceWith (
2013-07-23 18:42:45 +02:00
'<span class="spinner centered"/>' ) ;
2013-06-02 00:21:06 +02:00
this . connect ( password ) ;
} ,
renderPasswordForm : function ( ) {
2013-07-23 18:42:45 +02:00
this . $el . find ( 'span.centered.spinner' ) . remove ( ) ;
2013-06-02 00:21:06 +02:00
this . $el . find ( '.chat-body' ) . append (
$ ( '<div class="chatroom-form-container">' +
'<form class="chatroom-form">' +
'<legend>' + _ _ ( 'This chatroom requires a password' ) + '</legend>' +
'<label>' + _ _ ( 'Password: ' ) + '<input type="password" name="password"/></label>' +
'<input type="submit" value="' + _ _ ( 'Submit' ) + '/>' +
'</form>' +
'</div>' ) ) ;
this . $el . find ( '.chatroom-form' ) . on ( 'submit' , $ . proxy ( this . submitPassword , this ) ) ;
} ,
showDisconnectMessage : function ( msg ) {
this . $el . find ( '.chat-area' ) . remove ( ) ;
this . $el . find ( '.participants' ) . remove ( ) ;
2013-07-23 18:42:45 +02:00
this . $el . find ( 'span.centered.spinner' ) . remove ( ) ;
2013-06-02 00:21:06 +02:00
this . $el . find ( '.chat-body' ) . append ( $ ( '<p>' + msg + '</p>' ) ) ;
} ,
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' ) ,
201 : _ _ ( 'A new room has been created' ) ,
210 : _ _ ( 'Your nickname has been changed' )
} ,
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" ) ,
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
} ,
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." )
} ,
showStatusMessages : function ( $el , is _self ) {
/ * 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
* See : http : //xmpp.org/registrar/mucstatus.html
* /
var $chat _content = this . $el . find ( '.chat-content' ) ,
$stats = $el . find ( 'status' ) ,
disconnect _msgs = [ ] ,
info _msgs = [ ] ,
action _msgs = [ ] ,
msgs , i ;
for ( i = 0 ; i < $stats . length ; i ++ ) {
var stat = $stats [ i ] . getAttribute ( 'code' ) ;
if ( is _self ) {
if ( _ . contains ( _ . keys ( this . disconnectMessages ) , stat ) ) {
disconnect _msgs . push ( this . disconnectMessages [ stat ] ) ;
}
} else {
if ( _ . contains ( _ . keys ( this . infoMessages ) , stat ) ) {
info _msgs . push ( this . infoMessages [ stat ] ) ;
} else if ( _ . contains ( _ . keys ( this . actionInfoMessages ) , stat ) ) {
action _msgs . push (
2013-08-26 16:21:32 +02:00
_ _ ( this . actionInfoMessages [ stat ] , Strophe . unescapeNode ( Strophe . getResourceFromJid ( $el . attr ( 'from' ) ) ) )
) ;
2013-06-02 00:21:06 +02:00
}
2013-05-12 13:53:37 +02:00
}
2013-06-02 00:21:06 +02:00
}
if ( disconnect _msgs . length > 0 ) {
for ( i = 0 ; i < disconnect _msgs . length ; i ++ ) {
this . showDisconnectMessage ( disconnect _msgs [ i ] ) ;
}
this . model . set ( 'connected' , false ) ;
return ;
}
this . renderChatArea ( ) ;
for ( i = 0 ; i < info _msgs . length ; i ++ ) {
$chat _content . append ( this . info _template ( { message : info _msgs [ i ] } ) ) ;
}
for ( i = 0 ; i < action _msgs . length ; i ++ ) {
$chat _content . append ( this . info _template ( { message : action _msgs [ i ] } ) ) ;
}
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 ) {
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 ) {
var nick = room . nick ,
$presence = $ ( presence ) ,
from = $presence . attr ( 'from' ) ,
is _self = ( $presence . find ( "status[code='110']" ) . length ) || ( from == room . name + '/' + Strophe . escapeNode ( nick ) ) ,
$item ;
if ( $presence . attr ( 'type' ) === 'error' ) {
this . model . set ( 'connected' , false ) ;
this . showErrorMessage ( $presence . find ( 'error' ) , room ) ;
} else {
this . model . set ( 'connected' , true ) ;
this . showStatusMessages ( $presence , is _self ) ;
if ( ! this . model . get ( 'connected' ) ) {
return true ;
}
if ( $presence . find ( "status[code='201']" ) . length ) {
// This is a new chatroom. We create an instant
// chatroom, and let the user manually set any
// configuration setting.
converse . connection . muc . createInstantRoom ( room . name ) ;
}
if ( is _self ) {
$item = $presence . find ( 'item' ) ;
if ( $item . length ) {
if ( $item . attr ( 'affiliation' ) == 'owner' ) {
this . $el . find ( 'a.configure-chatroom-button' ) . show ( ) ;
}
}
if ( $presence . find ( "status[code='210']" ) . length ) {
// check if server changed our nick
this . model . set ( { 'nick' : Strophe . getResourceFromJid ( from ) } ) ;
}
}
}
return true ;
} ,
onChatRoomMessage : function ( message ) {
var $message = $ ( message ) ,
body = $message . children ( 'body' ) . text ( ) ,
jid = $message . attr ( 'from' ) ,
$chat _content = this . $el . find ( '.chat-content' ) ,
resource = Strophe . getResourceFromJid ( jid ) ,
sender = resource && Strophe . unescapeNode ( resource ) || '' ,
delayed = $message . find ( 'delay' ) . length > 0 ,
subject = $message . children ( 'subject' ) . text ( ) ,
match , template , message _datetime , message _date , dates , isodate , stamp ;
if ( delayed ) {
stamp = $message . find ( 'delay' ) . attr ( 'stamp' ) ;
message _datetime = converse . parseISO8601 ( stamp ) ;
2013-05-12 13:53:37 +02:00
} else {
2013-06-02 00:21:06 +02:00
message _datetime = new Date ( ) ;
}
// If this message is on a different day than the one received
// prior, then indicate it on the chatbox.
dates = $chat _content . find ( "time" ) . map ( function ( ) { return $ ( this ) . attr ( "datetime" ) ; } ) . get ( ) ;
message _date = new Date ( message _datetime . getTime ( ) ) ;
message _date . setUTCHours ( 0 , 0 , 0 , 0 ) ;
isodate = converse . toISOString ( message _date ) ;
if ( _ . indexOf ( dates , isodate ) == - 1 ) {
$chat _content . append ( this . new _day _template ( {
isodate : isodate ,
datestring : message _date . toString ( ) . substring ( 0 , 15 )
2013-05-12 13:53:37 +02:00
} ) ) ;
2013-05-11 18:14:06 +02:00
}
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!
2013-08-25 12:06:53 +02:00
$chat _content . append (
this . info _template ( {
'message' : _ _ ( 'Topic set by %1$s to: %2$s' , sender , subject )
} ) ) ;
2013-06-02 00:21:06 +02:00
}
if ( ! body ) { return true ; }
2013-08-25 12:06:53 +02:00
this . showMessage ( $chat _content ,
2013-06-02 00:21:06 +02:00
{ 'message' : body ,
'sender' : sender === this . model . get ( 'nick' ) && 'me' || 'room' ,
'fullname' : sender ,
'time' : converse . toISOString ( message _datetime )
} ) ;
return true ;
} ,
occupant _template : _ . template (
'<li class="{{role}}" ' +
'{[ if (role === "moderator") { ]}' +
'title="' + _ _ ( 'This user is a moderator' ) + '"' +
'{[ } ]}' +
'{[ if (role === "participant") { ]}' +
'title="' + _ _ ( 'This user can send messages in this room' ) + '"' +
'{[ } ]}' +
'{[ if (role === "visitor") { ]}' +
'title="' + _ _ ( 'This user can NOT send messages in this room' ) + '"' +
'{[ } ]}' +
'>{{nick}}</li>'
) ,
onChatRoomRoster : function ( roster , room ) {
this . renderChatArea ( ) ;
var controlboxview = converse . chatboxesview . views . controlbox ,
roster _size = _ . size ( roster ) ,
$participant _list = this . $el . find ( '.participant-list' ) ,
participants = [ ] , keys = _ . keys ( roster ) , i ;
this . $el . find ( '.participant-list' ) . empty ( ) ;
for ( i = 0 ; i < roster _size ; i ++ ) {
participants . push (
this . occupant _template ( {
role : roster [ keys [ i ] ] . role ,
nick : Strophe . unescapeNode ( keys [ i ] )
} ) ) ;
}
$participant _list . append ( participants . join ( "" ) ) ;
return true ;
2013-05-11 16:19:42 +02:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2013-05-12 13:53:37 +02:00
2013-06-02 00:21:06 +02:00
this . ChatBoxes = Backbone . Collection . extend ( {
model : converse . ChatBox ,
2013-05-12 13:53:37 +02:00
2013-06-02 00:21:06 +02:00
onConnected : function ( ) {
this . localStorage = new Backbone . LocalStorage (
hex _sha1 ( 'converse.chatboxes-' + converse . bare _jid ) ) ;
if ( ! this . get ( 'controlbox' ) ) {
this . add ( {
id : 'controlbox' ,
box _id : 'controlbox'
} ) ;
2013-05-12 13:53:37 +02:00
} else {
2013-06-02 00:21:06 +02:00
this . get ( 'controlbox' ) . save ( ) ;
2013-05-12 13:53:37 +02:00
}
2013-06-02 00:21:06 +02:00
// This will make sure the Roster is set up
this . get ( 'controlbox' ) . set ( { connected : true } ) ;
2013-10-03 18:43:49 +02:00
// Register message handler
converse . connection . addHandler (
$ . proxy ( function ( message ) {
this . messageReceived ( message ) ;
return true ;
} , this ) , null , 'message' , 'chat' ) ;
2013-06-02 00:21:06 +02:00
// Get cached chatboxes from localstorage
this . fetch ( {
add : true ,
success : $ . proxy ( function ( collection , resp ) {
if ( _ . include ( _ . pluck ( resp , 'id' ) , 'controlbox' ) ) {
// If the controlbox was saved in localstorage, it must be visible
this . get ( 'controlbox' ) . set ( { visible : true } ) . save ( ) ;
}
} , 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
2013-06-02 00:21:06 +02:00
messageReceived : function ( message ) {
var partner _jid , $message = $ ( message ) ,
message _from = $message . attr ( 'from' ) ;
if ( message _from == converse . connection . jid ) {
// FIXME: Forwarded messages should be sent to specific resources,
// not broadcasted
return true ;
}
var $forwarded = $message . children ( 'forwarded' ) ;
if ( $forwarded . length ) {
$message = $forwarded . children ( 'message' ) ;
}
var from = Strophe . getBareJidFromJid ( message _from ) ,
to = Strophe . getBareJidFromJid ( $message . attr ( 'to' ) ) ,
resource , chatbox , roster _item ;
if ( from == converse . bare _jid ) {
// I am the sender, so this must be a forwarded message...
partner _jid = to ;
resource = Strophe . getResourceFromJid ( $message . attr ( 'to' ) ) ;
} else {
partner _jid = from ;
resource = Strophe . getResourceFromJid ( message _from ) ;
}
chatbox = this . get ( partner _jid ) ;
roster _item = converse . roster . get ( partner _jid ) ;
2013-10-03 16:05:21 +02:00
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-09-08 15:54:04 +02:00
converse . log ( 'Could not get roster item for JID ' + partner _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
2013-06-02 00:21:06 +02:00
if ( ! chatbox ) {
chatbox = this . create ( {
'id' : partner _jid ,
'jid' : partner _jid ,
'fullname' : roster _item . get ( 'fullname' ) || jid ,
'image_type' : roster _item . get ( 'image_type' ) ,
'image' : roster _item . get ( 'image' ) ,
'url' : roster _item . get ( 'url' )
} ) ;
}
chatbox . messageReceived ( message ) ;
converse . roster . addResource ( partner _jid , resource ) ;
return true ;
}
} ) ;
2013-05-12 13:53:37 +02:00
2013-06-02 00:21:06 +02:00
this . ChatBoxesView = Backbone . View . extend ( {
el : '#collective-xmpp-chat-data' ,
initialize : function ( ) {
// boxesviewinit
this . views = { } ;
this . model . on ( "add" , function ( item ) {
var view = this . views [ item . get ( 'id' ) ] ;
if ( ! view ) {
if ( item . get ( 'chatroom' ) ) {
view = new converse . ChatRoomView ( { 'model' : item } ) ;
} else if ( item . get ( 'box_id' ) === 'controlbox' ) {
view = new converse . ControlBoxView ( { model : item } ) ;
view . render ( ) ;
} else {
view = new converse . ChatBoxView ( { model : item } ) ;
}
this . views [ item . get ( 'id' ) ] = view ;
} else {
delete view . model ; // Remove ref to old model to help garbage collection
view . model = item ;
view . initialize ( ) ;
if ( item . get ( 'id' ) !== 'controlbox' ) {
// FIXME: Why is it necessary to again append chatboxes?
view . $el . appendTo ( this . $el ) ;
}
}
} , this ) ;
2013-08-22 21:43:34 +02:00
} ,
showChatBox : function ( attrs ) {
var chatbox = this . model . get ( attrs . jid ) ;
if ( chatbox ) {
chatbox . trigger ( 'show' ) ;
} else {
chatbox = this . model . create ( attrs , {
'error' : function ( model , response ) {
converse . log ( response . responseText ) ;
}
} ) ;
}
return chatbox ;
2013-06-02 00:21:06 +02:00
}
} ) ;
this . RosterItem = Backbone . Model . extend ( {
initialize : function ( attributes , options ) {
var jid = attributes . jid ;
if ( ! attributes . fullname ) {
attributes . fullname = jid ;
}
var attrs = _ . extend ( {
'id' : jid ,
'user_id' : Strophe . getNodeFromJid ( jid ) ,
'resources' : [ ] ,
'status' : ''
} , attributes ) ;
attrs . sorted = false ;
attrs . chat _status = 'offline' ;
this . set ( attrs ) ;
}
} ) ;
this . RosterItemView = Backbone . View . extend ( {
tagName : 'dd' ,
events : {
"click .accept-xmpp-request" : "acceptRequest" ,
"click .decline-xmpp-request" : "declineRequest" ,
"click .open-chat" : "openChat" ,
"click .remove-xmpp-contact" : "removeContact"
} ,
openChat : function ( ev ) {
ev . preventDefault ( ) ;
2013-08-22 21:43:34 +02:00
converse . chatboxesview . showChatBox ( {
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 ) {
ev . preventDefault ( ) ;
var result = confirm ( "Are you sure you want to remove this contact?" ) ;
if ( result === true ) {
var bare _jid = this . model . get ( 'jid' ) ;
converse . connection . roster . remove ( bare _jid , function ( iq ) {
converse . connection . roster . unauthorize ( bare _jid ) ;
converse . rosterview . model . remove ( bare _jid ) ;
} ) ;
}
} ,
acceptRequest : function ( ev ) {
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' ) ) ;
} ) ;
ev . preventDefault ( ) ;
} ,
declineRequest : function ( ev ) {
ev . preventDefault ( ) ;
converse . connection . roster . unauthorize ( this . model . get ( 'jid' ) ) ;
this . model . destroy ( ) ;
} ,
template : _ . template (
2013-08-27 11:19:18 +02:00
'<a class="open-chat" title="' + _ _ ( 'Click to chat with this contact' ) + '" href="#">' +
'<span class="icon-{{ chat_status }}" title="{{ status_desc }}"></span>{{ fullname }}' +
'</a>' +
2013-08-29 20:06:17 +02:00
'<a class="remove-xmpp-contact icon-remove" title="' + _ _ ( 'Click to remove this contact' ) + '" href="#"></a>' ) ,
2013-06-02 00:21:06 +02:00
pending _template : _ . template (
2013-08-15 18:32:07 +02:00
'<span>{{ fullname }}</span>' +
2013-08-29 20:06:17 +02:00
'<a class="remove-xmpp-contact icon-remove" title="' + _ _ ( 'Click to remove this contact' ) + '" href="#"></a>' ) ,
2013-06-02 00:21:06 +02:00
request _template : _ . template ( '<div>{{ fullname }}</div>' +
2013-08-15 18:32:07 +02:00
'<button type="button" class="accept-xmpp-request">' +
'Accept</button>' +
'<button type="button" class="decline-xmpp-request">' +
'Decline</button>' +
'' ) ,
2013-06-02 00:21:06 +02:00
render : function ( ) {
var item = this . model ,
ask = item . get ( 'ask' ) ,
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 ) ;
2013-06-02 00:21:06 +02:00
this . $el . addClass ( item . get ( 'chat_status' ) ) ;
2013-10-05 22:16:09 +02:00
if ( ask === 'subscribe' ) {
2013-06-02 00:21:06 +02:00
this . $el . addClass ( 'pending-xmpp-contact' ) ;
this . $el . html ( this . pending _template ( item . toJSON ( ) ) ) ;
2013-10-05 22:16:09 +02:00
} else if ( ask === 'request' ) {
2013-06-02 00:21:06 +02:00
this . $el . addClass ( 'requesting-xmpp-contact' ) ;
this . $el . html ( this . request _template ( item . toJSON ( ) ) ) ;
converse . 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' ) ;
2013-08-15 20:14:00 +02:00
this . $el . html ( this . template (
2013-10-05 22:34:47 +02:00
_ . extend ( item . toJSON ( ) , { 'status_desc' : STATUSES [ item . get ( 'chat_status' ) || 'offline' ] } )
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
} ) ;
this . RosterItems = Backbone . Collection . extend ( {
model : converse . RosterItem ,
comparator : function ( rosteritem ) {
var chat _status = rosteritem . get ( 'chat_status' ) ,
rank = 4 ;
switch ( chat _status ) {
case 'offline' :
rank = 0 ;
break ;
case 'unavailable' :
rank = 1 ;
break ;
case 'xa' :
rank = 2 ;
break ;
case 'away' :
rank = 3 ;
break ;
case 'dnd' :
rank = 4 ;
break ;
case 'online' :
rank = 5 ;
break ;
}
return rank ;
} ,
subscribeToSuggestedItems : function ( msg ) {
$ ( msg ) . find ( 'item' ) . each ( function ( ) {
var $this = $ ( this ) ,
jid = $this . attr ( 'jid' ) ,
action = $this . attr ( 'action' ) ,
fullname = $this . attr ( 'name' ) ;
if ( action === 'add' ) {
converse . connection . roster . add ( jid , fullname , [ ] , function ( iq ) {
converse . connection . roster . subscribe ( jid , null , converse . xmppstatus . get ( 'fullname' ) ) ;
} ) ;
}
} ) ;
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 ,
models = this . models ,
models _length = models . length ,
i ;
for ( i = 0 ; i < models _length ; i ++ ) {
if ( _ . indexOf ( [ 'offline' , 'unavailable' ] , models [ i ] . get ( 'chat_status' ) ) === - 1 ) {
count ++ ;
2013-05-30 21:24:00 +02:00
}
}
2013-06-02 00:21:06 +02:00
return count ;
} ,
cleanCache : function ( items ) {
/ * 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 .
* /
var id , i ,
roster _ids = [ ] ;
for ( i = 0 ; i < items . length ; ++ i ) {
roster _ids . push ( items [ i ] . jid ) ;
2013-05-30 21:24:00 +02:00
}
2013-06-02 00:21:06 +02:00
for ( i = 0 ; i < this . models . length ; ++ i ) {
id = this . models [ i ] . get ( 'id' ) ;
if ( _ . indexOf ( roster _ids , id ) === - 1 ) {
2013-10-08 08:45:17 +02:00
this . get ( id ) . 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
2013-06-02 00:21:06 +02:00
rosterHandler : function ( items ) {
this . cleanCache ( items ) ;
_ . each ( items , function ( item , index , items ) {
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 ) {
is _last = false ;
if ( index === ( items . length - 1 ) ) { is _last = true ; }
this . create ( {
jid : item . jid ,
subscription : item . subscription ,
ask : item . ask ,
fullname : item . name || item . jid ,
is _last : is _last
} ) ;
} else {
if ( ( item . subscription === 'none' ) && ( item . ask === null ) ) {
// This user is no longer in our roster
model . destroy ( ) ;
} else if ( model . get ( 'subscription' ) !== item . subscription || model . get ( 'ask' ) !== item . ask ) {
// only modify model attributes if they are different from the
// ones that were already set when the rosterItem was added
model . set ( { 'subscription' : item . subscription , 'ask' : item . ask } ) ;
model . save ( ) ;
}
}
} , 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 ,
subscription : 'none' ,
ask : 'request' ,
fullname : fullname ,
image : img ,
image _type : img _type ,
url : url ,
vcard _updated : converse . toISOString ( new Date ( ) ) ,
is _last : true
} ) ;
} , this ) ,
$ . proxy ( function ( jid , fullname , img , img _type , url ) {
converse . log ( "Error while retrieving vcard" ) ;
// XXX: Should vcard_updated be set here as
// well?
this . add ( { jid : bare _jid , subscription : 'none' , ask : 'request' , fullname : jid , is _last : true } ) ;
} , 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' ) {
// TODO
// error presence stanzas don't necessarily have a 'from' attr.
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' ) ,
item ;
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.
// FIXME: We should ideally differentiate between converse.js using
// resources and other resources (i.e Pidgin etc.)
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
}
2013-10-08 08:45:17 +02:00
item = this . get ( bare _jid ) ;
2013-06-02 00:21:06 +02:00
if ( item && ( status _message . text ( ) != item . get ( 'status' ) ) ) {
item . 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 ) {
if ( item ) {
item . set ( { 'chat_status' : 'offline' } ) ;
}
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
} else if ( item ) {
// presence_type is undefined
this . addResource ( bare _jid , resource ) ;
item . set ( { '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
2013-06-02 00:21:06 +02:00
this . RosterView = Backbone . View . extend ( {
tagName : 'dl' ,
id : 'converse-roster' ,
rosteritemviews : { } ,
2013-03-24 10:18:26 +01:00
2013-10-03 14:22:33 +02:00
requesting _contacts _template : _ . template (
'<dt id="xmpp-contact-requests">' + _ _ ( 'Contact requests' ) + '</dt>' ) ,
contacts _template : _ . template (
'<dt id="xmpp-contacts">' + _ _ ( 'My contacts' ) + '</dt>' ) ,
pending _contacts _template : _ . template (
'<dt id="pending-xmpp-contacts">' + _ _ ( 'Pending contacts' ) + '</dt>' ) ,
2013-06-02 00:21:06 +02:00
initialize : function ( ) {
this . model . on ( "add" , function ( item ) {
2013-10-07 09:08:11 +02:00
this . addRosterItemView ( item ) . render ( item ) ;
2013-09-26 11:44:50 +02:00
if ( ! item . get ( 'vcard_updated' ) ) {
// This will update the vcard, which triggers a change
// request which will rerender the roster item.
converse . getVCard ( item . get ( 'jid' ) ) ;
}
2013-06-02 00:21:06 +02:00
} , this ) ;
2013-10-07 09:08:11 +02:00
this . model . on ( 'change' , function ( item ) {
2013-06-02 00:21:06 +02:00
if ( ( _ . size ( item . changed ) === 1 ) && _ . contains ( _ . keys ( item . changed ) , 'sorted' ) ) {
return ;
2013-03-24 13:01:55 +01:00
}
2013-10-07 09:08:11 +02:00
this . updateChatBox ( item ) . render ( item ) ;
2013-06-02 00:21:06 +02:00
} , this ) ;
2012-07-09 18:47:50 +02:00
2013-10-07 09:08:11 +02:00
this . model . on ( "remove" , function ( item ) { this . removeRosterItemView ( item ) ; } , this ) ;
this . model . on ( "destroy" , function ( item ) { this . removeRosterItemView ( item ) ; } , this ) ;
2012-07-15 13:10:11 +02:00
2013-10-05 23:07:42 +02:00
var roster _markup = this . contacts _template ( ) ;
2013-10-03 14:22:33 +02:00
if ( converse . allow _contact _requests ) {
roster _markup = this . requesting _contacts _template ( ) + roster _markup + this . pending _contacts _template ( ) ;
}
this . $el . hide ( ) . html ( roster _markup ) ;
2013-10-08 22:10:00 +02:00
this . model . fetch ( { add : true } ) ; // Get the cached roster items from localstorage
2013-06-02 00:21:06 +02:00
} ,
2012-07-15 21:03:34 +02:00
2013-06-02 00:21:06 +02:00
updateChatBox : function ( item , changed ) {
var chatbox = converse . chatboxes . get ( item . get ( 'jid' ) ) ,
changes = { } ;
2013-10-07 09:08:11 +02:00
if ( ! chatbox ) {
return this ;
}
2013-03-27 18:33:53 +01:00
if ( _ . has ( item . changed , 'chat_status' ) ) {
2013-06-02 00:21:06 +02:00
changes . chat _status = item . get ( 'chat_status' ) ;
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
if ( _ . has ( item . changed , 'status' ) ) {
changes . status = item . 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 ;
} ,
addRosterItemView : function ( item ) {
var view = new converse . RosterItemView ( { model : item } ) ;
this . rosteritemviews [ item . id ] = view ;
return this ;
} ,
removeRosterItemView : function ( item ) {
var view = this . rosteritemviews [ item . id ] ;
if ( view ) {
view . $el . remove ( ) ;
delete this . rosteritemviews [ item . id ] ;
this . render ( ) ;
}
return this ;
2013-06-02 00:21:06 +02:00
} ,
2013-10-05 23:07:42 +02:00
renderRosterItem : function ( item , view ) {
2013-10-07 09:08:11 +02:00
if ( ( converse . show _only _online _users ) && ( item . get ( 'chat_status' ) !== 'online' ) ) {
view . $el . remove ( ) ;
view . delegateEvents ( ) ;
return this ;
2013-10-05 23:07:42 +02:00
}
2013-10-05 22:34:47 +02:00
if ( $ . contains ( document . documentElement , view . el ) ) {
view . render ( ) ;
} else {
2013-10-05 23:07:42 +02:00
this . $el . find ( '#xmpp-contacts' ) . after ( view . render ( ) . el ) ;
2013-10-05 22:34:47 +02:00
}
} ,
2013-06-02 00:21:06 +02:00
render : function ( item ) {
var $my _contacts = this . $el . find ( '#xmpp-contacts' ) ,
$contact _requests = this . $el . find ( '#xmpp-contact-requests' ) ,
$pending _contacts = this . $el . find ( '#pending-xmpp-contacts' ) ,
2013-08-29 21:56:56 +02:00
sorted = false ,
$count , changed _presence ;
2013-06-02 00:21:06 +02:00
if ( item ) {
var jid = item . id ,
view = this . rosteritemviews [ item . id ] ,
ask = item . get ( 'ask' ) ,
subscription = item . get ( 'subscription' ) ,
crit = { order : 'asc' } ;
if ( ask === 'subscribe' ) {
$pending _contacts . after ( view . render ( ) . el ) ;
$pending _contacts . after ( $pending _contacts . siblings ( 'dd.pending-xmpp-contact' ) . tsort ( crit ) ) ;
} else if ( ask === 'request' ) {
$contact _requests . after ( view . render ( ) . el ) ;
$contact _requests . after ( $contact _requests . siblings ( 'dd.requesting-xmpp-contact' ) . tsort ( crit ) ) ;
} else if ( subscription === 'both' || subscription === 'to' ) {
2013-10-05 23:07:42 +02:00
this . renderRosterItem ( item , view ) ;
2013-06-02 00:21:06 +02:00
}
2013-10-07 09:08:11 +02:00
changed _presence = item . changed . chat _status ;
2013-08-29 21:56:56 +02:00
if ( changed _presence ) {
2013-09-13 10:57:28 +02:00
this . sortRoster ( changed _presence ) ;
2013-08-29 21:56:56 +02:00
sorted = true ;
2013-09-26 11:44:50 +02:00
}
2013-08-29 21:24:47 +02:00
if ( item . get ( 'is_last' ) ) {
2013-08-29 21:56:56 +02:00
if ( ! sorted ) {
this . sortRoster ( item . get ( 'chat_status' ) ) ;
}
2013-08-29 21:24:47 +02:00
if ( ! this . $el . is ( ':visible' ) ) {
// Once all initial roster items have been added, we
// can show the roster.
this . $el . show ( ) ;
}
2013-06-02 00:21:06 +02:00
}
2013-03-12 09:56:50 +01:00
}
2013-06-02 00:21:06 +02:00
// Hide the headings if there are no contacts under them
_ . each ( [ $my _contacts , $contact _requests , $pending _contacts ] , function ( h ) {
if ( h . nextUntil ( 'dt' ) . length ) {
2013-08-29 21:24:47 +02:00
if ( ! h . is ( ':visible' ) ) {
h . show ( ) ;
}
2013-06-02 00:21:06 +02:00
}
2013-08-29 21:24:47 +02:00
else if ( h . is ( ':visible' ) ) {
2013-06-02 00:21:06 +02:00
h . hide ( ) ;
2013-02-21 19:57:22 +01:00
}
2013-06-02 00:21:06 +02:00
} ) ;
$count = $ ( '#online-count' ) ;
$count . text ( '(' + this . model . getNumOnlineContacts ( ) + ')' ) ;
if ( ! $count . is ( ':visible' ) ) {
$count . show ( ) ;
2012-09-21 16:04:57 +02:00
}
2013-06-02 00:21:06 +02:00
return this ;
} ,
2012-07-28 22:46:14 +02:00
2013-08-29 21:56:56 +02:00
sortRoster : function ( chat _status ) {
var $my _contacts = this . $el . find ( '#xmpp-contacts' ) ;
$my _contacts . siblings ( 'dd.current-xmpp-contact.' + chat _status ) . tsort ( 'a' , { order : 'asc' } ) ;
$my _contacts . after ( $my _contacts . siblings ( 'dd.current-xmpp-contact.offline' ) ) ;
$my _contacts . after ( $my _contacts . siblings ( 'dd.current-xmpp-contact.unavailable' ) ) ;
2013-09-13 10:57:28 +02:00
$my _contacts . after ( $my _contacts . siblings ( 'dd.current-xmpp-contact.xa' ) ) ;
2013-08-29 21:56:56 +02:00
$my _contacts . after ( $my _contacts . siblings ( 'dd.current-xmpp-contact.away' ) ) ;
$my _contacts . after ( $my _contacts . siblings ( 'dd.current-xmpp-contact.dnd' ) ) ;
$my _contacts . after ( $my _contacts . siblings ( 'dd.current-xmpp-contact.online' ) ) ;
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-08-04 15:39:46 +02:00
this . on ( 'change' , $ . proxy ( function ( ) {
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 )
) ;
}
} , 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 ( {
url : 'set-custom-status' ,
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"
} ,
toggleOptions : function ( ev ) {
ev . preventDefault ( ) ;
$ ( ev . target ) . parent ( ) . parent ( ) . siblings ( 'dd' ) . find ( 'ul' ) . toggle ( 'fast' ) ;
} ,
change _status _message _template : _ . template (
'<form id="set-custom-xmpp-status">' +
'<input type="text" class="custom-xmpp-status" {{ status_message }}"' +
'placeholder="' + _ _ ( 'Custom status' ) + '"/>' +
'<button type="submit">' + _ _ ( 'Save' ) + '</button>' +
'</form>' ) ,
status _template : _ . template (
'<div class="xmpp-status">' +
'<a class="choose-xmpp-status {{ chat_status }}" data-value="{{status_message}}" href="#" title="' + _ _ ( 'Click to change your chat status' ) + '">' +
2013-08-15 20:14:00 +02:00
'<span class="icon-{{ chat_status }}"></span>' +
2013-06-02 00:21:06 +02:00
'{{ status_message }}' +
'</a>' +
2013-08-26 10:54:58 +02:00
'<a class="change-xmpp-status-message icon-pencil" href="#" title="' + _ _ ( 'Click here to write a custom status message' ) + '"></a>' +
2013-06-02 00:21:06 +02:00
'</div>' ) ,
renderStatusChangeForm : function ( ev ) {
ev . preventDefault ( ) ;
var status _message = this . model . get ( 'status' ) || 'offline' ;
var input = this . change _status _message _template ( { 'status_message' : status _message } ) ;
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
if ( status _message === "" ) {
}
this . model . setStatusMessage ( status _message ) ;
} ,
setStatus : function ( ev ) {
ev . preventDefault ( ) ;
var $el = $ ( ev . target ) ,
value = $el . attr ( 'data-value' ) ;
this . model . setStatus ( value ) ;
this . $el . find ( ".dropdown dd ul" ) . hide ( ) ;
} ,
2013-03-12 09:56:50 +01:00
2013-06-02 00:21:06 +02:00
getPrettyStatus : function ( stat ) {
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 {
pretty _status = _ _ ( stat ) || _ _ ( 'online' ) ; // XXX: Is 'online' the right default choice here?
}
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 (
this . status _template ( {
'chat_status' : stat ,
'status_message' : status _message
} ) ) ;
} ,
2012-12-09 20:47:12 +01:00
2013-06-02 00:21:06 +02:00
choose _template : _ . template (
'<dl id="target" class="dropdown">' +
'<dt id="fancy-xmpp-status-select" class="fancy-dropdown"></dt>' +
2013-08-15 20:14:00 +02:00
'<dd><ul class="xmpp-status-menu"></ul></dd>' +
2013-06-02 00:21:06 +02:00
'</dl>' ) ,
2013-02-19 17:16:05 +01:00
2013-06-02 00:21:06 +02:00
option _template : _ . template (
'<li>' +
2013-08-15 20:14:00 +02:00
'<a href="#" class="{{ value }}" data-value="{{ value }}">' +
'<span class="icon-{{ value }}"></span>' +
'{{ text }}' +
'</a>' +
2013-06-02 00:21:06 +02:00
'</li>' ) ,
2012-12-09 20:47:12 +01:00
2013-06-02 00:21:06 +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 ( this . choose _template ( ) ) ;
this . $el . find ( '#fancy-xmpp-status-select' )
. html ( this . status _template ( {
2013-08-04 15:39:46 +02:00
'status_message' : this . model . get ( 'status_message' ) || _ _ ( "I am %1$s" , this . getPrettyStatus ( chat _status ) ) ,
2013-06-02 00:21:06 +02:00
'chat_status' : chat _status
} ) ) ;
// iterate through all the <option> elements and add option values
options . each ( function ( ) {
options _list . push ( that . option _template ( { '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-03-25 12:08:27 +01:00
}
2013-06-02 00:21:06 +02:00
} ) ;
2012-12-09 20:47:12 +01:00
2013-06-02 00:21:06 +02:00
this . Feature = Backbone . Model . extend ( ) ;
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 ( ) {
this . localStorage = new Backbone . LocalStorage (
hex _sha1 ( 'converse.features' + converse . bare _jid ) ) ;
if ( this . localStorage . records . length === 0 ) {
// localStorage is empty, so we've likely never queried this
// 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
} ,
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'
} ,
tab _template : _ . template (
'<li><a class="current" href="#login">' + _ _ ( 'Sign in' ) + '</a></li>' ) ,
template : _ . template (
'<form id="converse-login">' +
'<label>' + _ _ ( 'XMPP/Jabber Username:' ) + '</label>' +
2013-09-02 11:48:09 +02:00
'<input type="username" name="jid">' +
2013-06-02 00:21:06 +02:00
'<label>' + _ _ ( 'Password:' ) + '</label>' +
2013-09-02 11:48:09 +02:00
'<input type="password" name="password">' +
2013-06-02 00:21:06 +02:00
'<input class="login-submit" type="submit" value="' + _ _ ( 'Log In' ) + '">' +
'</form">' ) ,
bosh _url _input : _ . template (
'<label>' + _ _ ( 'BOSH Service URL:' ) + '</label>' +
'<input type="text" id="bosh_service_url">' ) ,
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
}
2013-08-24 02:20:00 +02:00
converse . connection = new Strophe . Connection ( converse . bosh _service _url ) ;
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-24 16:11:08 +02:00
showConnectButton : function ( ) {
var $form = this . $el . find ( '#converse-login' ) ;
2013-10-05 22:34:47 +02:00
var $button = $form . find ( 'input[type=submit]' ) ;
2013-08-24 16:11:08 +02:00
if ( $button . length ) {
$button . show ( ) . siblings ( 'span' ) . remove ( ) ;
}
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 ) {
2013-09-02 11:48:09 +02:00
cfg . $parent . html ( this . $el . html ( this . template ( ) ) ) ;
2013-08-02 12:26:16 +02:00
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
2013-08-24 16:11:08 +02:00
this . model . on ( 'connection-fail' , function ( ) { this . showConnectButton ( ) ; } , this ) ;
this . model . on ( 'auth-fail' , function ( ) { this . showConnectButton ( ) ; } , this ) ;
2013-08-02 12:26:16 +02:00
} ,
render : function ( ) {
this . $tabs . append ( this . tab _template ( ) ) ;
this . $el . find ( 'input#jid' ) . focus ( ) ;
return this ;
} ,
2013-06-02 00:21:06 +02:00
authenticate : function ( ev ) {
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-05 22:34:47 +02:00
// Initialization
// --------------
2013-04-14 00:47:18 +02:00
2013-06-02 00:21:06 +02:00
// This is the end of the initialize method.
2013-04-20 11:32:54 +02:00
this . chatboxes = new this . ChatBoxes ( ) ;
this . chatboxesview = new this . ChatBoxesView ( { model : this . chatboxes } ) ;
2013-06-02 22:32:33 +02:00
$ ( '.toggle-online-users' ) . bind (
2013-04-20 11:32:54 +02:00
'click' ,
$ . proxy ( function ( e ) {
e . preventDefault ( ) ; this . toggleControlBox ( ) ;
} , this )
) ;
2013-08-24 03:10:06 +02:00
if ( ( this . prebind ) && ( ! this . connection ) ) {
if ( ( ! this . jid ) || ( ! this . sid ) || ( ! this . rid ) || ( ! this . bosh _service _url ) ) {
this . log ( 'If you set prebind=true, you MUST supply JID, RID and SID values' ) ;
return ;
2013-08-23 00:48:53 +02:00
}
2013-08-24 03:10:06 +02:00
this . connection = new Strophe . Connection ( this . bosh _service _url ) ;
this . connection . attach ( this . jid , this . sid , this . rid , this . onConnect ) ;
} else if ( this . connection ) {
this . onConnected ( ) ;
2013-06-02 21:40:05 +02:00
}
2013-08-24 02:29:13 +02:00
if ( this . show _controlbox _by _default ) { this . showControlBox ( ) ; }
2013-04-20 11:32:54 +02:00
} ;
2013-08-05 09:25:29 +02:00
return {
2013-08-24 03:10:06 +02:00
'initialize' : function ( settings , callback ) {
converse . initialize ( settings , callback ) ;
2013-08-05 09:25:29 +02:00
}
} ;
2012-09-21 16:04:57 +02:00
} ) ) ;