2016-02-26 18:28:18 +01:00
/ * *
2016-09-21 17:24:32 +02:00
* @ license almond 0.3 . 3 Copyright jQuery Foundation and other contributors .
2016-05-03 17:37:10 +02:00
* Released under MIT license , http : //github.com/requirejs/almond/LICENSE
2016-02-26 18:28:18 +01:00
* /
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*global setTimeout: false */
var requirejs , require , define ;
( function ( undef ) {
var main , req , makeMap , handlers ,
defined = { } ,
waiting = { } ,
config = { } ,
defining = { } ,
hasOwn = Object . prototype . hasOwnProperty ,
aps = [ ] . slice ,
jsSuffixRegExp = /\.js$/ ;
function hasProp ( obj , prop ) {
return hasOwn . call ( obj , prop ) ;
}
/ * *
* Given a relative module name , like . / something , normalize it to
* a real name that can be mapped to a path .
* @ param { String } name the relative name
* @ param { String } baseName a real name that the name arg is relative
* to .
* @ returns { String } normalized name
* /
function normalize ( name , baseName ) {
var nameParts , nameSegment , mapValue , foundMap , lastIndex ,
2016-05-03 17:37:10 +02:00
foundI , foundStarMap , starI , i , j , part , normalizedBaseParts ,
2016-02-26 18:28:18 +01:00
baseParts = baseName && baseName . split ( "/" ) ,
map = config . map ,
starMap = ( map && map [ '*' ] ) || { } ;
//Adjust any relative paths.
2016-05-03 17:37:10 +02:00
if ( name ) {
name = name . split ( '/' ) ;
lastIndex = name . length - 1 ;
// If wanting node ID compatibility, strip .js from end
// of IDs. Have to do this here, and not in nameToUrl
// because node allows either .js or non .js to map
// to same file.
if ( config . nodeIdCompat && jsSuffixRegExp . test ( name [ lastIndex ] ) ) {
name [ lastIndex ] = name [ lastIndex ] . replace ( jsSuffixRegExp , '' ) ;
}
// Starts with a '.' so need the baseName
if ( name [ 0 ] . charAt ( 0 ) === '.' && baseParts ) {
//Convert baseName to array, and lop off the last part,
//so that . matches that 'directory' and not name of the baseName's
//module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts . slice ( 0 , baseParts . length - 1 ) ;
name = normalizedBaseParts . concat ( name ) ;
}
//start trimDots
for ( i = 0 ; i < name . length ; i ++ ) {
part = name [ i ] ;
if ( part === '.' ) {
name . splice ( i , 1 ) ;
i -= 1 ;
} else if ( part === '..' ) {
// If at the start, or previous value is still ..,
// keep them so that when converted to a path it may
// still work when converted to a path, even though
// as an ID it is less than ideal. In larger point
// releases, may be better to just kick out an error.
if ( i === 0 || ( i === 1 && name [ 2 ] === '..' ) || name [ i - 1 ] === '..' ) {
continue ;
} else if ( i > 0 ) {
name . splice ( i - 1 , 2 ) ;
i -= 2 ;
2016-02-26 18:28:18 +01:00
}
}
}
2016-05-03 17:37:10 +02:00
//end trimDots
name = name . join ( '/' ) ;
2016-02-26 18:28:18 +01:00
}
//Apply map config if available.
if ( ( baseParts || starMap ) && map ) {
nameParts = name . split ( '/' ) ;
for ( i = nameParts . length ; i > 0 ; i -= 1 ) {
nameSegment = nameParts . slice ( 0 , i ) . join ( "/" ) ;
if ( baseParts ) {
//Find the longest baseName segment match in the config.
//So, do joins on the biggest to smallest lengths of baseParts.
for ( j = baseParts . length ; j > 0 ; j -= 1 ) {
mapValue = map [ baseParts . slice ( 0 , j ) . join ( '/' ) ] ;
//baseName segment has config, find if it has one for
//this name.
if ( mapValue ) {
mapValue = mapValue [ nameSegment ] ;
if ( mapValue ) {
//Match, update name to the new value.
foundMap = mapValue ;
foundI = i ;
break ;
}
}
}
}
if ( foundMap ) {
break ;
}
//Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching
//config, then favor over this star map.
if ( ! foundStarMap && starMap && starMap [ nameSegment ] ) {
foundStarMap = starMap [ nameSegment ] ;
starI = i ;
}
}
if ( ! foundMap && foundStarMap ) {
foundMap = foundStarMap ;
foundI = starI ;
}
if ( foundMap ) {
nameParts . splice ( 0 , foundI , foundMap ) ;
name = nameParts . join ( '/' ) ;
}
}
return name ;
}
function makeRequire ( relName , forceSync ) {
return function ( ) {
//A version of a require function that passes a moduleName
//value for items that may need to
//look up paths relative to the moduleName
var args = aps . call ( arguments , 0 ) ;
//If first arg is not require('string'), and there is only
//one arg, it is the array form without a callback. Insert
//a null so that the following concat is correct.
if ( typeof args [ 0 ] !== 'string' && args . length === 1 ) {
args . push ( null ) ;
}
return req . apply ( undef , args . concat ( [ relName , forceSync ] ) ) ;
} ;
}
function makeNormalize ( relName ) {
return function ( name ) {
return normalize ( name , relName ) ;
} ;
}
function makeLoad ( depName ) {
return function ( value ) {
defined [ depName ] = value ;
} ;
}
function callDep ( name ) {
if ( hasProp ( waiting , name ) ) {
var args = waiting [ name ] ;
delete waiting [ name ] ;
defining [ name ] = true ;
main . apply ( undef , args ) ;
}
if ( ! hasProp ( defined , name ) && ! hasProp ( defining , name ) ) {
throw new Error ( 'No ' + name ) ;
}
return defined [ name ] ;
}
//Turns a plugin!resource to [plugin, resource]
//with the plugin being undefined if the name
//did not have a plugin prefix.
function splitPrefix ( name ) {
var prefix ,
index = name ? name . indexOf ( '!' ) : - 1 ;
if ( index > - 1 ) {
prefix = name . substring ( 0 , index ) ;
name = name . substring ( index + 1 , name . length ) ;
}
return [ prefix , name ] ;
}
2016-09-21 17:24:32 +02:00
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts ( relName ) {
return relName ? splitPrefix ( relName ) : [ ] ;
}
2016-02-26 18:28:18 +01:00
/ * *
* Makes a name map , normalizing the name , and using a plugin
* for normalization if necessary . Grabs a ref to plugin
* too , as an optimization .
* /
2016-09-21 17:24:32 +02:00
makeMap = function ( name , relParts ) {
2016-02-26 18:28:18 +01:00
var plugin ,
parts = splitPrefix ( name ) ,
2016-09-21 17:24:32 +02:00
prefix = parts [ 0 ] ,
relResourceName = relParts [ 1 ] ;
2016-02-26 18:28:18 +01:00
name = parts [ 1 ] ;
if ( prefix ) {
2016-09-21 17:24:32 +02:00
prefix = normalize ( prefix , relResourceName ) ;
2016-02-26 18:28:18 +01:00
plugin = callDep ( prefix ) ;
}
//Normalize according
if ( prefix ) {
if ( plugin && plugin . normalize ) {
2016-09-21 17:24:32 +02:00
name = plugin . normalize ( name , makeNormalize ( relResourceName ) ) ;
2016-02-26 18:28:18 +01:00
} else {
2016-09-21 17:24:32 +02:00
name = normalize ( name , relResourceName ) ;
2016-02-26 18:28:18 +01:00
}
} else {
2016-09-21 17:24:32 +02:00
name = normalize ( name , relResourceName ) ;
2016-02-26 18:28:18 +01:00
parts = splitPrefix ( name ) ;
prefix = parts [ 0 ] ;
name = parts [ 1 ] ;
if ( prefix ) {
plugin = callDep ( prefix ) ;
}
}
//Using ridiculous property names for space reasons
return {
f : prefix ? prefix + '!' + name : name , //fullName
n : name ,
pr : prefix ,
p : plugin
} ;
} ;
function makeConfig ( name ) {
return function ( ) {
return ( config && config . config && config . config [ name ] ) || { } ;
} ;
}
handlers = {
require : function ( name ) {
return makeRequire ( name ) ;
} ,
exports : function ( name ) {
var e = defined [ name ] ;
if ( typeof e !== 'undefined' ) {
return e ;
} else {
return ( defined [ name ] = { } ) ;
}
} ,
module : function ( name ) {
return {
id : name ,
uri : '' ,
exports : defined [ name ] ,
config : makeConfig ( name )
} ;
}
} ;
main = function ( name , deps , callback , relName ) {
2016-09-21 17:24:32 +02:00
var cjsModule , depName , ret , map , i , relParts ,
2016-02-26 18:28:18 +01:00
args = [ ] ,
callbackType = typeof callback ,
usingExports ;
//Use name if no relName
relName = relName || name ;
2016-09-21 17:24:32 +02:00
relParts = makeRelParts ( relName ) ;
2016-02-26 18:28:18 +01:00
//Call the callback to define the module, if necessary.
if ( callbackType === 'undefined' || callbackType === 'function' ) {
//Pull out the defined dependencies and pass the ordered
//values to the callback.
//Default to [require, exports, module] if no deps
deps = ! deps . length && callback . length ? [ 'require' , 'exports' , 'module' ] : deps ;
for ( i = 0 ; i < deps . length ; i += 1 ) {
2016-09-21 17:24:32 +02:00
map = makeMap ( deps [ i ] , relParts ) ;
2016-02-26 18:28:18 +01:00
depName = map . f ;
//Fast path CommonJS standard dependencies.
if ( depName === "require" ) {
args [ i ] = handlers . require ( name ) ;
} else if ( depName === "exports" ) {
//CommonJS module spec 1.1
args [ i ] = handlers . exports ( name ) ;
usingExports = true ;
} else if ( depName === "module" ) {
//CommonJS module spec 1.1
cjsModule = args [ i ] = handlers . module ( name ) ;
} else if ( hasProp ( defined , depName ) ||
hasProp ( waiting , depName ) ||
hasProp ( defining , depName ) ) {
args [ i ] = callDep ( depName ) ;
} else if ( map . p ) {
map . p . load ( map . n , makeRequire ( relName , true ) , makeLoad ( depName ) , { } ) ;
args [ i ] = defined [ depName ] ;
} else {
throw new Error ( name + ' missing ' + depName ) ;
}
}
ret = callback ? callback . apply ( defined [ name ] , args ) : undefined ;
if ( name ) {
//If setting exports via "module" is in play,
//favor that over return value and exports. After that,
//favor a non-undefined return value over exports use.
if ( cjsModule && cjsModule . exports !== undef &&
cjsModule . exports !== defined [ name ] ) {
defined [ name ] = cjsModule . exports ;
} else if ( ret !== undef || ! usingExports ) {
//Use the return value from the function.
defined [ name ] = ret ;
}
}
} else if ( name ) {
//May just be an object definition for the module. Only
//worry about defining if have a module name.
defined [ name ] = callback ;
}
} ;
requirejs = require = req = function ( deps , callback , relName , forceSync , alt ) {
if ( typeof deps === "string" ) {
if ( handlers [ deps ] ) {
//callback in this case is really relName
return handlers [ deps ] ( callback ) ;
}
//Just return the module wanted. In this scenario, the
//deps arg is the module name, and second arg (if passed)
//is just the relName.
//Normalize module name, if it contains . or ..
2016-09-21 17:24:32 +02:00
return callDep ( makeMap ( deps , makeRelParts ( callback ) ) . f ) ;
2016-02-26 18:28:18 +01:00
} else if ( ! deps . splice ) {
//deps is a config object, not an array.
config = deps ;
if ( config . deps ) {
req ( config . deps , config . callback ) ;
}
if ( ! callback ) {
return ;
}
if ( callback . splice ) {
//callback is an array, which means it is a dependency list.
//Adjust args if there are dependencies
deps = callback ;
callback = relName ;
relName = null ;
} else {
deps = undef ;
}
}
//Support require(['a'])
callback = callback || function ( ) { } ;
//If relName is a function, it is an errback handler,
//so remove it.
if ( typeof relName === 'function' ) {
relName = forceSync ;
forceSync = alt ;
}
//Simulate async callback;
if ( forceSync ) {
main ( undef , deps , callback , relName ) ;
} else {
//Using a non-zero value because of concern for what old browsers
//do, and latest browsers "upgrade" to 4 if lower value is used:
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
//If want a value immediately, use require('id') instead -- something
//that works in almond on the global level, but not guaranteed and
//unlikely to work in other AMD implementations.
setTimeout ( function ( ) {
main ( undef , deps , callback , relName ) ;
} , 4 ) ;
}
return req ;
} ;
/ * *
* Just drops the config on the floor , but returns req in case
* the config return value is used .
* /
req . config = function ( cfg ) {
return req ( cfg ) ;
} ;
/ * *
* Expose module registry for debugging and tooling
* /
requirejs . _defined = defined ;
define = function ( name , deps , callback ) {
if ( typeof name !== 'string' ) {
throw new Error ( 'See almond README: incorrect module build, no module name' ) ;
}
//This module may not have dependencies
if ( ! deps . splice ) {
//deps is not an array, so probably means
//an object literal or factory function for
//the value. Adjust args.
callback = deps ;
deps = [ ] ;
}
if ( ! hasProp ( defined , name ) && ! hasProp ( waiting , name ) ) {
waiting [ name ] = [ name , deps , callback ] ;
}
} ;
define . amd = {
jQuery : true
} ;
} ( ) ) ;
2016-09-16 14:35:02 +02:00
define ( "almond" , function ( ) { } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
/ * *
2016-09-21 17:24:32 +02:00
* @ license RequireJS text 2.0 . 12 Copyright ( c ) 2010 - 2014 , The Dojo Foundation All Rights Reserved .
* Available via the MIT or new BSD license .
* see : http : //github.com/requirejs/text for details
2016-02-28 10:00:22 +01:00
* /
/*jslint regexp: true */
/ * g l o b a l r e q u i r e , X M L H t t p R e q u e s t , A c t i v e X O b j e c t ,
define , window , process , Packages ,
java , location , Components , FileUtils * /
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'text' , [ 'module' ] , function ( module ) {
'use strict' ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
var text , fs , Cc , Ci , xpcIsWindows ,
progIds = [ 'Msxml2.XMLHTTP' , 'Microsoft.XMLHTTP' , 'Msxml2.XMLHTTP.4.0' ] ,
xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im ,
bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im ,
hasLocation = typeof location !== 'undefined' && location . href ,
defaultProtocol = hasLocation && location . protocol && location . protocol . replace ( /\:/ , '' ) ,
defaultHostName = hasLocation && location . hostname ,
defaultPort = hasLocation && ( location . port || undefined ) ,
buildMap = { } ,
masterConfig = ( module . config && module . config ( ) ) || { } ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
text = {
2016-09-21 17:24:32 +02:00
version : '2.0.12' ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
strip : function ( content ) {
//Strips <?xml ...?> declarations so that external SVG and XML
//documents can be added to a document without worry. Also, if the string
//is an HTML document, only the part inside the body tag is returned.
if ( content ) {
content = content . replace ( xmlRegExp , "" ) ;
var matches = content . match ( bodyRegExp ) ;
if ( matches ) {
content = matches [ 1 ] ;
}
} else {
content = "" ;
}
return content ;
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
jsEscape : function ( content ) {
return content . replace ( /(['\\])/g , '\\$1' )
. replace ( /[\f]/g , "\\f" )
. replace ( /[\b]/g , "\\b" )
. replace ( /[\n]/g , "\\n" )
. replace ( /[\t]/g , "\\t" )
. replace ( /[\r]/g , "\\r" )
. replace ( /[\u2028]/g , "\\u2028" )
. replace ( /[\u2029]/g , "\\u2029" ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
createXhr : masterConfig . createXhr || function ( ) {
//Would love to dump the ActiveX crap in here. Need IE 6 to die first.
var xhr , i , progId ;
if ( typeof XMLHttpRequest !== "undefined" ) {
return new XMLHttpRequest ( ) ;
} else if ( typeof ActiveXObject !== "undefined" ) {
for ( i = 0 ; i < 3 ; i += 1 ) {
progId = progIds [ i ] ;
try {
xhr = new ActiveXObject ( progId ) ;
} catch ( e ) { }
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( xhr ) {
progIds = [ progId ] ; // so faster next time
break ;
}
}
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
return xhr ;
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
/ * *
* Parses a resource name into its component parts . Resource names
* look like : module / name . ext ! strip , where the ! strip part is
* optional .
* @ param { String } name the resource name
* @ returns { Object } with properties "moduleName" , "ext" and "strip"
* where strip is a boolean .
* /
parseName : function ( name ) {
var modName , ext , temp ,
strip = false ,
2016-09-21 17:24:32 +02:00
index = name . indexOf ( "." ) ,
2016-02-28 10:00:22 +01:00
isRelative = name . indexOf ( './' ) === 0 ||
name . indexOf ( '../' ) === 0 ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( index !== - 1 && ( ! isRelative || index > 1 ) ) {
modName = name . substring ( 0 , index ) ;
2016-09-21 17:24:32 +02:00
ext = name . substring ( index + 1 , name . length ) ;
2016-02-28 10:00:22 +01:00
} else {
modName = name ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
temp = ext || modName ;
index = temp . indexOf ( "!" ) ;
if ( index !== - 1 ) {
//Pull off the strip arg.
strip = temp . substring ( index + 1 ) === "strip" ;
temp = temp . substring ( 0 , index ) ;
if ( ext ) {
ext = temp ;
} else {
modName = temp ;
}
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
return {
moduleName : modName ,
ext : ext ,
strip : strip
} ;
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
xdRegExp : /^((\w+)\:)?\/\/([^\/\\]+)/ ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
/ * *
* Is an URL on another domain . Only works for browser use , returns
* false in non - browser environments . Only used to know if an
* optimized . js version of a text resource should be loaded
* instead .
* @ param { String } url
* @ returns Boolean
* /
useXhr : function ( url , protocol , hostname , port ) {
var uProtocol , uHostName , uPort ,
match = text . xdRegExp . exec ( url ) ;
if ( ! match ) {
return true ;
}
uProtocol = match [ 2 ] ;
uHostName = match [ 3 ] ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
uHostName = uHostName . split ( ':' ) ;
uPort = uHostName [ 1 ] ;
uHostName = uHostName [ 0 ] ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
return ( ! uProtocol || uProtocol === protocol ) &&
( ! uHostName || uHostName . toLowerCase ( ) === hostname . toLowerCase ( ) ) &&
2016-09-21 17:24:32 +02:00
( ( ! uPort && ! uHostName ) || uPort === port ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
finishLoad : function ( name , strip , content , onLoad ) {
content = strip ? text . strip ( content ) : content ;
if ( masterConfig . isBuild ) {
buildMap [ name ] = content ;
}
onLoad ( content ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
load : function ( name , req , onLoad , config ) {
//Name has format: some.module.filext!strip
//The strip part is optional.
//if strip is present, then that means only get the string contents
//inside a body tag in an HTML string. For XML/SVG content it means
//removing the <?xml ...?> declarations so the content can be inserted
//into the current doc without problems.
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Do not bother with the work if a build and text will
// not be inlined.
if ( config && config . isBuild && ! config . inlineText ) {
onLoad ( ) ;
return ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
masterConfig . isBuild = config && config . isBuild ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
var parsed = text . parseName ( name ) ,
nonStripName = parsed . moduleName +
( parsed . ext ? '.' + parsed . ext : '' ) ,
url = req . toUrl ( nonStripName ) ,
useXhr = ( masterConfig . useXhr ) ||
text . useXhr ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Do not load if it is an empty: url
if ( url . indexOf ( 'empty:' ) === 0 ) {
onLoad ( ) ;
return ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
//Load the text. Use XHR if possible and in a browser.
if ( ! hasLocation || useXhr ( url , defaultProtocol , defaultHostName , defaultPort ) ) {
text . get ( url , function ( content ) {
text . finishLoad ( name , parsed . strip , content , onLoad ) ;
} , function ( err ) {
if ( onLoad . error ) {
onLoad . error ( err ) ;
}
} ) ;
} else {
//Need to fetch the resource across domains. Assume
//the resource has been optimized into a JS module. Fetch
//by the module name + extension, but do not include the
//!strip part to avoid file system issues.
req ( [ nonStripName ] , function ( content ) {
text . finishLoad ( parsed . moduleName + '.' + parsed . ext ,
parsed . strip , content , onLoad ) ;
} ) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
write : function ( pluginName , moduleName , write , config ) {
if ( buildMap . hasOwnProperty ( moduleName ) ) {
var content = text . jsEscape ( buildMap [ moduleName ] ) ;
write . asModule ( pluginName + "!" + moduleName ,
"define(function () { return '" +
content +
"';});\n" ) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
writeFile : function ( pluginName , moduleName , req , write , config ) {
var parsed = text . parseName ( moduleName ) ,
extPart = parsed . ext ? '.' + parsed . ext : '' ,
nonStripName = parsed . moduleName + extPart ,
//Use a '.js' file name so that it indicates it is a
//script that can be loaded across domains.
fileName = req . toUrl ( parsed . moduleName + extPart ) + '.js' ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
//Leverage own load() method to load plugin value, but only
//write out values that do not have the strip argument,
//to avoid any potential issues with ! in file names.
text . load ( nonStripName , req , function ( value ) {
//Use own write() method to construct full module value.
//But need to create shell that translates writeFile's
//write() to the right interface.
var textWrite = function ( contents ) {
return write ( fileName , contents ) ;
} ;
textWrite . asModule = function ( moduleName , contents ) {
return write . asModule ( moduleName , fileName , contents ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
text . write ( pluginName , nonStripName , textWrite , config ) ;
} , config ) ;
}
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( masterConfig . env === 'node' || ( ! masterConfig . env &&
typeof process !== "undefined" &&
process . versions &&
! ! process . versions . node &&
2016-09-21 17:24:32 +02:00
! process . versions [ 'node-webkit' ] ) ) {
2016-02-28 10:00:22 +01:00
//Using special require.nodeRequire, something added by r.js.
fs = require . nodeRequire ( 'fs' ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
text . get = function ( url , callback , errback ) {
try {
var file = fs . readFileSync ( url , 'utf8' ) ;
//Remove BOM (Byte Mark Order) from utf8 files if it is there.
2016-09-21 17:24:32 +02:00
if ( file . indexOf ( '\uFEFF' ) === 0 ) {
2016-02-28 10:00:22 +01:00
file = file . substring ( 1 ) ;
}
callback ( file ) ;
} catch ( e ) {
if ( errback ) {
errback ( e ) ;
}
}
} ;
} else if ( masterConfig . env === 'xhr' || ( ! masterConfig . env &&
text . createXhr ( ) ) ) {
text . get = function ( url , callback , errback , headers ) {
var xhr = text . createXhr ( ) , header ;
xhr . open ( 'GET' , url , true ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
//Allow plugins direct access to xhr headers
if ( headers ) {
for ( header in headers ) {
if ( headers . hasOwnProperty ( header ) ) {
xhr . setRequestHeader ( header . toLowerCase ( ) , headers [ header ] ) ;
}
}
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
//Allow overrides specified in config
if ( masterConfig . onXhr ) {
masterConfig . onXhr ( xhr , url ) ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
xhr . onreadystatechange = function ( evt ) {
var status , err ;
//Do not explicitly handle errors, those should be
//visible via console output in the browser.
if ( xhr . readyState === 4 ) {
status = xhr . status || 0 ;
if ( status > 399 && status < 600 ) {
//An http 4xx or 5xx error. Signal an error.
err = new Error ( url + ' HTTP status: ' + status ) ;
err . xhr = xhr ;
if ( errback ) {
errback ( err ) ;
}
} else {
callback ( xhr . responseText ) ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( masterConfig . onXhrComplete ) {
masterConfig . onXhrComplete ( xhr , url ) ;
}
}
} ;
xhr . send ( null ) ;
} ;
} else if ( masterConfig . env === 'rhino' || ( ! masterConfig . env &&
typeof Packages !== 'undefined' && typeof java !== 'undefined' ) ) {
//Why Java, why is this so awkward?
text . get = function ( url , callback ) {
var stringBuffer , line ,
encoding = "utf-8" ,
file = new java . io . File ( url ) ,
lineSeparator = java . lang . System . getProperty ( "line.separator" ) ,
input = new java . io . BufferedReader ( new java . io . InputStreamReader ( new java . io . FileInputStream ( file ) , encoding ) ) ,
content = '' ;
try {
stringBuffer = new java . lang . StringBuffer ( ) ;
line = input . readLine ( ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
// http://www.unicode.org/faq/utf_bom.html
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
if ( line && line . length ( ) && line . charAt ( 0 ) === 0xfeff ) {
// Eat the BOM, since we've already found the encoding on this file,
// and we plan to concatenating this buffer with others; the BOM should
// only appear at the top of a file.
line = line . substring ( 1 ) ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( line !== null ) {
stringBuffer . append ( line ) ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
while ( ( line = input . readLine ( ) ) !== null ) {
stringBuffer . append ( lineSeparator ) ;
stringBuffer . append ( line ) ;
}
//Make sure we return a JavaScript string and not a Java string.
content = String ( stringBuffer . toString ( ) ) ; //String
} finally {
input . close ( ) ;
}
callback ( content ) ;
} ;
} else if ( masterConfig . env === 'xpconnect' || ( ! masterConfig . env &&
typeof Components !== 'undefined' && Components . classes &&
Components . interfaces ) ) {
//Avert your gaze!
Cc = Components . classes ;
Ci = Components . interfaces ;
Components . utils [ 'import' ] ( 'resource://gre/modules/FileUtils.jsm' ) ;
xpcIsWindows = ( '@mozilla.org/windows-registry-key;1' in Cc ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
text . get = function ( url , callback ) {
var inStream , convertStream , fileObj ,
readData = { } ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( xpcIsWindows ) {
url = url . replace ( /\//g , '\\' ) ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
fileObj = new FileUtils . File ( url ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
//XPCOM, you so crazy
try {
inStream = Cc [ '@mozilla.org/network/file-input-stream;1' ]
. createInstance ( Ci . nsIFileInputStream ) ;
inStream . init ( fileObj , 1 , 0 , false ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
convertStream = Cc [ '@mozilla.org/intl/converter-input-stream;1' ]
. createInstance ( Ci . nsIConverterInputStream ) ;
convertStream . init ( inStream , "utf-8" , inStream . available ( ) ,
Ci . nsIConverterInputStream . DEFAULT _REPLACEMENT _CHARACTER ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
convertStream . readString ( inStream . available ( ) , readData ) ;
convertStream . close ( ) ;
inStream . close ( ) ;
callback ( readData . value ) ;
} catch ( e ) {
throw new Error ( ( fileObj && fileObj . path || '' ) + ': ' + e ) ;
}
} ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
return text ;
} ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// RequireJS UnderscoreJS template plugin
// http://github.com/jfparadis/requirejs-tpl
//
// An alternative to http://github.com/ZeeAgency/requirejs-tpl
//
// Using UnderscoreJS micro-templates at http://underscorejs.org/#template
// Using and RequireJS text.js at http://requirejs.org/docs/api.html#text
// @author JF Paradis
// @version 0.0.2
//
// Released under the MIT license
//
// Usage:
// require(['backbone', 'tpl!mytemplate'], function (Backbone, mytemplate) {
// return Backbone.View.extend({
// initialize: function(){
// this.render();
// },
// render: function(){
// this.$el.html(mytemplate({message: 'hello'}));
// });
// });
//
// Configuration: (optional)
// require.config({
// tpl: {
// extension: '.tpl' // default = '.html'
// }
// });
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
/*jslint nomen: true */
/*global define: false */
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl' , [ 'text' , 'underscore' ] , function ( text , _ ) {
'use strict' ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
var buildMap = { } ,
buildTemplateSource = "define('{pluginName}!{moduleName}', function () { return {source}; });\n" ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
return {
version : '0.0.2' ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
load : function ( moduleName , parentRequire , onload , config ) {
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( config . tpl && config . tpl . templateSettings ) {
_ . templateSettings = config . tpl . templateSettings ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
if ( buildMap [ moduleName ] ) {
onload ( buildMap [ moduleName ] ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
} else {
2016-09-21 17:24:32 +02:00
var ext = config . tpl && ! _ . isUndefined ( config . tpl . extension ) ? config . tpl . extension : '.html' ;
2016-02-28 10:00:22 +01:00
var path = ( config . tpl && config . tpl . path ) || '' ;
text . load ( path + moduleName + ext , parentRequire , function ( source ) {
buildMap [ moduleName ] = _ . template ( source ) ;
onload ( buildMap [ moduleName ] ) ;
} , config ) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
write : function ( pluginName , moduleName , write ) {
var build = buildMap [ moduleName ] ,
source = build && build . source ;
if ( source ) {
write . asModule ( pluginName + '!' + moduleName ,
buildTemplateSource
. replace ( '{pluginName}' , pluginName )
. replace ( '{moduleName}' , moduleName )
. replace ( '{source}' , source ) ) ;
}
2016-02-26 18:28:18 +01:00
}
} ;
2016-02-28 10:00:22 +01:00
} ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!action' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="chat-message ' +
( ( _ _t = ( extra _classes ) ) == null ? '' : _ _t ) +
'" data-isodate="' +
( ( _ _t = ( isodate ) ) == null ? '' : _ _t ) +
'">\n <span class="chat-msg-author chat-msg-' +
( ( _ _t = ( sender ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( time ) ) == null ? '' : _ _t ) +
' **' +
( ( _ _t = ( username ) ) == null ? '' : _ _t ) +
' </span>\n <span class="chat-msg-content">' +
( ( _ _t = ( message ) ) == null ? '' : _ _t ) +
'</span>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!add_contact_dropdown' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<dl class="add-converse-contact dropdown">\n <dt id="xmpp-contact-search" class="fancy-dropdown">\n <a class="toggle-xmpp-contact-form icon-plus" href="#" title="' +
( ( _ _t = ( label _click _to _chat ) ) == null ? '' : _ _t ) +
'"> ' +
( ( _ _t = ( label _add _contact ) ) == null ? '' : _ _t ) +
'</a>\n </dt>\n <dd class="search-xmpp" style="display:none"><ul></ul></dd>\n</dl>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!add_contact_form' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<li>\n <form class="pure-form add-xmpp-contact">\n <input type="text"\n name="identifier"\n class="username"\n placeholder="' +
( ( _ _t = ( label _contact _username ) ) == null ? '' : _ _t ) +
'"/>\n <button class="pure-button button-primary" type="submit">' +
( ( _ _t = ( label _add ) ) == null ? '' : _ _t ) +
'</button>\n </form>\n</li>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!change_status_message' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<form id="set-custom-xmpp-status" class="pure-form">\n<fieldset>\n <span class="input-button-group">\n <input type="text" class="custom-xmpp-status" ' +
( ( _ _t = ( status _message ) ) == null ? '' : _ _t ) +
' placeholder="' +
( ( _ _t = ( label _custom _status ) ) == null ? '' : _ _t ) +
'"/>\n <input type="submit" class="pure-button button-primary" value="' +
( ( _ _t = ( label _save ) ) == null ? '' : _ _t ) +
'"/>\n </span>\n</fieldset>\n</form>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chat_status' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="xmpp-status">\n <a class="choose-xmpp-status ' +
( ( _ _t = ( chat _status ) ) == null ? '' : _ _t ) +
' icon-' +
( ( _ _t = ( chat _status ) ) == null ? '' : _ _t ) +
'" data-value="' +
( ( _ _t = ( status _message ) ) == null ? '' : _ _t ) +
'" href="#" title="' +
( ( _ _t = ( desc _change _status ) ) == null ? '' : _ _t ) +
'">\n ' +
( ( _ _t = ( status _message ) ) == null ? '' : _ _t ) +
'\n </a>\n <a class="change-xmpp-status-message icon-pencil" href="#" title="' +
( ( _ _t = ( desc _custom _status ) ) == null ? '' : _ _t ) +
'"></a>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chatarea' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-06-20 21:11:43 +02:00
_ _p += '<div class="chat-area">\n <div class="chat-content"></div>\n <div class="new-msgs-indicator hidden">▼ ' +
( ( _ _t = ( unread _msgs ) ) == null ? '' : _ _t ) +
' ▼</div>\n <form class="sendXMPPMessage" action="" method="post">\n ' ;
2016-02-28 10:00:22 +01:00
if ( show _toolbar ) {
_ _p += '\n <ul class="chat-toolbar no-text-select"></ul>\n ' ;
}
_ _p += '\n <textarea type="text" class="chat-textarea" \n placeholder="' +
( ( _ _t = ( label _message ) ) == null ? '' : _ _t ) +
'"/>\n </form>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chatbox' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="flyout box-flyout">\n <div class="dragresize dragresize-top"></div>\n <div class="dragresize dragresize-topleft"></div>\n <div class="dragresize dragresize-left"></div>\n <div class="chat-head chat-head-chatbox">\n <a class="chatbox-btn close-chatbox-button icon-close" title="' +
( ( _ _t = ( info _close ) ) == null ? '' : _ _t ) +
'"></a>\n <div class="chat-title">\n ' ;
if ( url ) {
_ _p += '\n <a href="' +
( ( _ _t = ( url ) ) == null ? '' : _ _t ) +
2016-03-16 12:49:35 +01:00
'" target="_blank" rel="noopener" class="user">\n ' ;
2016-02-28 10:00:22 +01:00
}
_ _p += '\n ' +
2016-03-16 12:49:35 +01:00
( ( _ _t = ( title ) ) == null ? '' : _ _t ) +
2016-02-28 10:00:22 +01:00
'\n ' ;
if ( url ) {
_ _p += '\n </a>\n ' ;
}
2016-06-20 21:11:43 +02:00
_ _p += '\n </div>\n <p class="user-custom-message"><p/>\n </div>\n <div class="chat-body">\n <div class="chat-content"></div>\n <div class="new-msgs-indicator hidden">▼ ' +
( ( _ _t = ( unread _msgs ) ) == null ? '' : _ _t ) +
' ▼</div>\n ' ;
2016-03-16 12:49:35 +01:00
if ( show _textarea ) {
_ _p += '\n <form class="sendXMPPMessage" action="" method="post">\n ' ;
2016-02-28 10:00:22 +01:00
if ( show _toolbar ) {
_ _p += '\n <ul class="chat-toolbar no-text-select"></ul>\n ' ;
}
_ _p += '\n <textarea\n type="text"\n class="chat-textarea"\n placeholder="' +
( ( _ _t = ( label _personal _message ) ) == null ? '' : _ _t ) +
2016-03-16 12:49:35 +01:00
'"/>\n </form>\n ' ;
}
_ _p += '\n </div>\n</div>\n' ;
2016-02-28 10:00:22 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chatroom' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-04-01 15:33:14 +02:00
_ _p += '<div class="flyout box-flyout">\n <div class="dragresize dragresize-top"></div>\n <div class="dragresize dragresize-topleft"></div>\n <div class="dragresize dragresize-left"></div>\n <div class="chat-head chat-head-chatroom">\n <a class="chatbox-btn close-chatbox-button icon-close"></a>\n <a class="chatbox-btn configure-chatroom-button icon-wrench" style="display:none"></a>\n <div class="chat-title"> ' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( _ . escape ( name ) ) ) == null ? '' : _ _t ) +
' </div>\n <p class="chatroom-topic"><p/>\n </div>\n <div class="chat-body chatroom-body"><span class="spinner centered"/></div>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chatroom_form' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="chatroom-form-container">\n <form class="pure-form pure-form-stacked converse-form chatroom-form">\n <fieldset>\n <span class="spinner centered"/>\n </fieldset>\n </form>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chatroom_password_form' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="chatroom-form-container">\n <form class="pure-form converse-form chatroom-form">\n <fieldset>\n <legend>' +
( ( _ _t = ( heading ) ) == null ? '' : _ _t ) +
'</legend>\n <label>' +
( ( _ _t = ( label _password ) ) == null ? '' : _ _t ) +
'</label>\n <input type="password" name="password"/>\n </fieldset>\n <fieldset>\n <input class="pure-button button-primary" type="submit" value="' +
( ( _ _t = ( label _submit ) ) == null ? '' : _ _t ) +
'"/>\n </fieldset>\n </form>\n</div>\n' ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-07-28 18:06:31 +02:00
define ( 'tpl!chatroom_nickname_form' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="chatroom-form-container">\n <form class="pure-form converse-form chatroom-form">\n <fieldset>\n <label>' +
( ( _ _t = ( heading ) ) == null ? '' : _ _t ) +
'</label>\n <p class="validation-message">' +
( ( _ _t = ( validation _message ) ) == null ? '' : _ _t ) +
'</p>\n <input type="text" required="required" name="nick" class="new-chatroom-nick" placeholder="' +
( ( _ _t = ( label _nickname ) ) == null ? '' : _ _t ) +
'"/>\n </fieldset>\n <fieldset>\n <input type="submit" class="pure-button button-primary" name="join" value="' +
( ( _ _t = ( label _join ) ) == null ? '' : _ _t ) +
'"/>\n </fieldset>\n </form>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!chatroom_sidebar' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-06-20 21:11:43 +02:00
_ _p += '<!-- <div class="occupants"> -->\n' ;
if ( allow _muc _invitations ) {
_ _p += '\n<form class="pure-form room-invite">\n <input class="invited-contact" placeholder="' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( label _invitation ) ) == null ? '' : _ _t ) +
2016-06-20 21:11:43 +02:00
'" type="text"/>\n</form>\n' ;
}
_ _p += '\n<p class="occupants-heading">' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( label _occupants ) ) == null ? '' : _ _t ) +
':</p>\n<ul class="occupant-list"></ul>\n<!-- </div> -->\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chatrooms_tab' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<li><a class="s" href="#chatrooms">' +
( ( _ _t = ( label _rooms ) ) == null ? '' : _ _t ) +
'</a></li>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!chats_panel' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div id="minimized-chats">\n <a id="toggle-minimized-chats" href="#"></a>\n <div class="flyout minimized-chats-flyout"></div>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!choose_status' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<dl id="target" class="dropdown">\n <dt id="fancy-xmpp-status-select" class="fancy-dropdown"></dt>\n <dd><ul class="xmpp-status-menu"></ul></dd>\n</dl>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!contacts_panel' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<form class="pure-form set-xmpp-status" action="" method="post">\n <span id="xmpp-status-holder">\n <select id="select-xmpp-status" style="display:none">\n <option value="online">' +
( ( _ _t = ( label _online ) ) == null ? '' : _ _t ) +
'</option>\n <option value="dnd">' +
( ( _ _t = ( label _busy ) ) == null ? '' : _ _t ) +
'</option>\n <option value="away">' +
( ( _ _t = ( label _away ) ) == null ? '' : _ _t ) +
'</option>\n ' ;
if ( include _offline _state ) {
_ _p += '\n <option value="offline">' +
( ( _ _t = ( label _offline ) ) == null ? '' : _ _t ) +
'</option>\n ' ;
}
_ _p += '\n ' ;
if ( allow _logout ) {
_ _p += '\n <option value="logout">' +
( ( _ _t = ( label _logout ) ) == null ? '' : _ _t ) +
'</option>\n ' ;
}
_ _p += '\n </select>\n </span>\n</form>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!contacts_tab' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<li><a class="s current" href="#users">' +
( ( _ _t = ( label _contacts ) ) == null ? '' : _ _t ) +
'</a></li>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!controlbox' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-04-01 15:33:14 +02:00
_ _p += '<div class="flyout box-flyout">\n <div class="dragresize dragresize-top"></div>\n <div class="dragresize dragresize-topleft"></div>\n <div class="dragresize dragresize-left"></div>\n <div class="chat-head controlbox-head">\n <ul id="controlbox-tabs"></ul>\n ' ;
if ( ! sticky _controlbox ) {
_ _p += '\n <a class="chatbox-btn close-chatbox-button icon-close"></a>\n ' ;
}
_ _p += '\n </div>\n <div class="controlbox-panes"></div>\n</div>\n' ;
2016-02-28 10:00:22 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!controlbox_toggle' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<span class="conn-feedback">' +
( ( _ _t = ( label _toggle ) ) == null ? '' : _ _t ) +
'</span>\n<span style="display: none" id="online-count">(0)</span>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!field' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<field var="' +
( ( _ _t = ( name ) ) == null ? '' : _ _t ) +
'">' ;
if ( _ . isArray ( value ) ) {
_ _p += '\n ' ;
_ . each ( value , function ( arrayValue ) {
_ _p += '<value>' +
( ( _ _t = ( arrayValue ) ) == null ? '' : _ _t ) +
'</value>' ;
} ) ;
_ _p += '\n' ;
} else {
_ _p += '\n <value>' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'</value>\n' ;
}
_ _p += '</field>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!form_captcha' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '' ;
if ( label ) {
_ _p += '\n<label>\n ' +
( ( _ _t = ( label ) ) == null ? '' : _ _t ) +
'\n</label>\n' ;
}
_ _p += '\n<img src="data:' +
( ( _ _t = ( type ) ) == null ? '' : _ _t ) +
';base64,' +
( ( _ _t = ( data ) ) == null ? '' : _ _t ) +
'">\n<input name="' +
( ( _ _t = ( name ) ) == null ? '' : _ _t ) +
'" type="text" ' ;
if ( required ) {
_ _p += ' class="required" ' ;
}
_ _p += ' >\n\n\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!form_checkbox' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<label>' +
( ( _ _t = ( label ) ) == null ? '' : _ _t ) +
'</label>\n<input name="' +
( ( _ _t = ( name ) ) == null ? '' : _ _t ) +
'" type="' +
( ( _ _t = ( type ) ) == null ? '' : _ _t ) +
'" ' +
( ( _ _t = ( checked ) ) == null ? '' : _ _t ) +
'>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!form_input' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '' ;
if ( label ) {
_ _p += '\n<label>\n ' +
( ( _ _t = ( label ) ) == null ? '' : _ _t ) +
'\n</label>\n' ;
}
_ _p += '\n<input name="' +
( ( _ _t = ( name ) ) == null ? '' : _ _t ) +
'" type="' +
( ( _ _t = ( type ) ) == null ? '' : _ _t ) +
'" \n ' ;
if ( value ) {
_ _p += ' value="' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'" ' ;
}
_ _p += '\n ' ;
if ( required ) {
_ _p += ' class="required" ' ;
}
_ _p += ' >\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!form_select' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<label>' +
( ( _ _t = ( label ) ) == null ? '' : _ _t ) +
'</label>\n<select name="' +
( ( _ _t = ( name ) ) == null ? '' : _ _t ) +
'" ' ;
if ( multiple ) {
_ _p += ' multiple="multiple" ' ;
}
_ _p += '>' +
( ( _ _t = ( options ) ) == null ? '' : _ _t ) +
'</select>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!form_textarea' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<label class="label-ta">' +
( ( _ _t = ( label ) ) == null ? '' : _ _t ) +
'</label>\n<textarea name="' +
( ( _ _t = ( name ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'</textarea>\n' ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!form_username' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '' ;
if ( label ) {
_ _p += '\n<label>\n ' +
( ( _ _t = ( label ) ) == null ? '' : _ _t ) +
'\n</label>\n' ;
}
_ _p += '\n<div class="input-group">\n <input name="' +
( ( _ _t = ( name ) ) == null ? '' : _ _t ) +
'" type="' +
( ( _ _t = ( type ) ) == null ? '' : _ _t ) +
'"\n ' ;
if ( value ) {
_ _p += ' value="' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'" ' ;
}
_ _p += '\n ' ;
if ( required ) {
_ _p += ' class="required" ' ;
}
_ _p += ' />\n <span title="' +
( ( _ _t = ( domain ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( domain ) ) == null ? '' : _ _t ) +
'</span>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!group_header' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<a href="#" class="group-toggle icon-' +
( ( _ _t = ( toggle _state ) ) == null ? '' : _ _t ) +
'" title="' +
( ( _ _t = ( desc _group _toggle ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( label _group ) ) == null ? '' : _ _t ) +
'</a>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!info' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="chat-info">' +
( ( _ _t = ( message ) ) == null ? '' : _ _t ) +
'</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!login_panel' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<form class="pure-form pure-form-stacked converse-form" id="converse-login" method="post">\n ' ;
if ( auto _login ) {
_ _p += '\n <span class="spinner login-submit"/>\n ' ;
}
_ _p += '\n ' ;
if ( ! auto _login ) {
_ _p += '\n ' ;
2016-09-16 14:35:02 +02:00
if ( authentication == LOGIN || authentication == EXTERNAL ) {
2016-02-28 10:00:22 +01:00
_ _p += '\n <label>' +
( ( _ _t = ( label _username ) ) == null ? '' : _ _t ) +
'</label>\n <input type="text" name="jid" placeholder="' +
( ( _ _t = ( placeholder _username ) ) == null ? '' : _ _t ) +
2016-09-16 14:35:02 +02:00
'">\n ' ;
if ( authentication !== EXTERNAL ) {
_ _p += '\n <label>' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( label _password ) ) == null ? '' : _ _t ) +
2016-09-16 14:35:02 +02:00
'</label>\n <input type="password" name="password" placeholder="' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( placeholder _password ) ) == null ? '' : _ _t ) +
2016-09-16 14:35:02 +02:00
'">\n ' ;
}
_ _p += '\n <input class="pure-button button-primary" type="submit" value="' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( label _login ) ) == null ? '' : _ _t ) +
'">\n <span class="conn-feedback"></span>\n ' ;
}
_ _p += '\n ' ;
if ( authentication == ANONYMOUS ) {
_ _p += '\n <input type="pure-button button-primary" class="submit login-anon" value="' +
( ( _ _t = ( label _anon _login ) ) == null ? '' : _ _t ) +
'"/>\n ' ;
}
_ _p += '\n ' ;
if ( authentication == PREBIND ) {
_ _p += '\n <p>Disconnected.</p>\n ' ;
}
_ _p += '\n ' ;
}
_ _p += '\n</form>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!login_tab' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<li><a class="current" href="#login-dialog">' +
( ( _ _t = ( label _sign _in ) ) == null ? '' : _ _t ) +
'</a></li>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!message' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<div class="chat-message ' +
( ( _ _t = ( extra _classes ) ) == null ? '' : _ _t ) +
'" data-isodate="' +
( ( _ _t = ( isodate ) ) == null ? '' : _ _t ) +
'" data-msgid="' +
( ( _ _t = ( msgid ) ) == null ? '' : _ _t ) +
'">\n <span class="chat-msg-author chat-msg-' +
( ( _ _t = ( sender ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( time ) ) == null ? '' : _ _t ) +
' ' +
( ( _ _t = ( username ) ) == null ? '' : _ _t ) +
': </span>\n <span class="chat-msg-content">' +
( ( _ _t = ( message ) ) == null ? '' : _ _t ) +
'</span>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!new_day' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<time class="chat-info chat-date" data-isodate="' +
( ( _ _t = ( isodate ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( datestring ) ) == null ? '' : _ _t ) +
'</time>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!occupant' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<li class="' +
( ( _ _t = ( role ) ) == null ? '' : _ _t ) +
2016-07-28 18:06:31 +02:00
' occupant" id="' +
( ( _ _t = ( id ) ) == null ? '' : _ _t ) +
2016-02-28 10:00:22 +01:00
'"\n ' ;
if ( role === "moderator" ) {
_ _p += '\n title="' +
( ( _ _t = ( desc _moderator ) ) == null ? '' : _ _t ) +
2016-07-28 18:06:31 +02:00
' ' +
( ( _ _t = ( hint _occupant ) ) == null ? '' : _ _t ) +
2016-02-28 10:00:22 +01:00
'"\n ' ;
}
_ _p += '\n ' ;
if ( role === "occupant" ) {
_ _p += '\n title="' +
( ( _ _t = ( desc _occupant ) ) == null ? '' : _ _t ) +
2016-07-28 18:06:31 +02:00
' ' +
( ( _ _t = ( hint _occupant ) ) == null ? '' : _ _t ) +
2016-02-28 10:00:22 +01:00
'"\n ' ;
}
_ _p += '\n ' ;
if ( role === "visitor" ) {
_ _p += '\n title="' +
( ( _ _t = ( desc _visitor ) ) == null ? '' : _ _t ) +
2016-07-28 18:06:31 +02:00
' ' +
( ( _ _t = ( hint _occupant ) ) == null ? '' : _ _t ) +
2016-02-28 10:00:22 +01:00
'"\n ' ;
}
2016-07-28 18:06:31 +02:00
_ _p += '>' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( nick ) ) == null ? '' : _ _t ) +
'</li>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!pending_contact' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '' ;
if ( allow _chat _pending _contacts ) {
_ _p += '\n<a class="open-chat"href="#">\n' ;
}
_ _p += '\n<span class="pending-contact-name" title="Name: ' +
( ( _ _t = ( fullname ) ) == null ? '' : _ _t ) +
'\nJID: ' +
( ( _ _t = ( jid ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( fullname ) ) == null ? '' : _ _t ) +
'</span> \n' ;
if ( allow _chat _pending _contacts ) {
_ _p += '\n</a>\n' ;
}
_ _p += '\n<a class="remove-xmpp-contact icon-remove" title="' +
( ( _ _t = ( desc _remove ) ) == null ? '' : _ _t ) +
'" href="#"></a>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!pending_contacts' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<dt class="roster-group" id="pending-xmpp-contacts"><a href="#" class="group-toggle icon-' +
( ( _ _t = ( toggle _state ) ) == null ? '' : _ _t ) +
'" title="' +
( ( _ _t = ( desc _group _toggle ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( label _pending _contacts ) ) == null ? '' : _ _t ) +
'</a></dt>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!register_panel' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<form id="converse-register" class="pure-form converse-form">\n <span class="reg-feedback"></span>\n <label>' +
( ( _ _t = ( label _domain ) ) == null ? '' : _ _t ) +
'</label>\n <input type="text" name="domain" placeholder="' +
( ( _ _t = ( domain _placeholder ) ) == null ? '' : _ _t ) +
'">\n <p class="form-help">' +
( ( _ _t = ( help _providers ) ) == null ? '' : _ _t ) +
' <a href="' +
( ( _ _t = ( href _providers ) ) == null ? '' : _ _t ) +
2016-03-16 12:49:35 +01:00
'" class="url" target="_blank" rel="noopener">' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( help _providers _link ) ) == null ? '' : _ _t ) +
'</a>.</p>\n <input class="pure-button button-primary" type="submit" value="' +
( ( _ _t = ( label _register ) ) == null ? '' : _ _t ) +
'">\n</form>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!register_tab' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<li><a class="s" href="#register">' +
( ( _ _t = ( label _register ) ) == null ? '' : _ _t ) +
'</a></li>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!registration_form' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<p class="provider-title">' +
( ( _ _t = ( domain ) ) == null ? '' : _ _t ) +
'</p>\n<a href=\'https://xmpp.net/result.php?domain=' +
( ( _ _t = ( domain ) ) == null ? '' : _ _t ) +
'&type=client\'>\n <img class="provider-score" src=\'https://xmpp.net/badge.php?domain=' +
( ( _ _t = ( domain ) ) == null ? '' : _ _t ) +
'\' alt=\'xmpp.net score\' />\n</a>\n<p class="title">' +
( ( _ _t = ( title ) ) == null ? '' : _ _t ) +
'</p>\n<p class="instructions">' +
( ( _ _t = ( instructions ) ) == null ? '' : _ _t ) +
'</p>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!registration_request' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<span class="spinner login-submit"/>\n<p class="info">' +
( ( _ _t = ( info _message ) ) == null ? '' : _ _t ) +
'</p>\n<button class="pure-button button-cancel hor_centered">' +
( ( _ _t = ( cancel ) ) == null ? '' : _ _t ) +
'</button>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!requesting_contact' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '' ;
if ( allow _chat _pending _contacts ) {
_ _p += '\n<a class="open-chat"href="#">\n' ;
}
_ _p += '\n<span class="req-contact-name" title="Name: ' +
( ( _ _t = ( fullname ) ) == null ? '' : _ _t ) +
'\nJID: ' +
( ( _ _t = ( jid ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( fullname ) ) == null ? '' : _ _t ) +
'</span>\n' ;
if ( allow _chat _pending _contacts ) {
_ _p += '\n</a>\n' ;
}
_ _p += '\n<span class="request-actions">\n <a class="accept-xmpp-request icon-checkmark" title="' +
( ( _ _t = ( desc _accept ) ) == null ? '' : _ _t ) +
'" href="#"></a>\n <a class="decline-xmpp-request icon-close" title="' +
( ( _ _t = ( desc _decline ) ) == null ? '' : _ _t ) +
'" href="#"></a>\n</span>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!requesting_contacts' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<dt class="roster-group" id="xmpp-contact-requests"><a href="#" class="group-toggle icon-' +
( ( _ _t = ( toggle _state ) ) == null ? '' : _ _t ) +
'" title="' +
( ( _ _t = ( desc _group _toggle ) ) == null ? '' : _ _t ) +
'">' +
( ( _ _t = ( label _contact _requests ) ) == null ? '' : _ _t ) +
'</a></dt>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!room_description' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<!-- FIXME: check markup in mockup -->\n<div class="room-info">\n<p class="room-info"><strong>' +
( ( _ _t = ( label _desc ) ) == null ? '' : _ _t ) +
'</strong> ' +
( ( _ _t = ( desc ) ) == null ? '' : _ _t ) +
'</p>\n<p class="room-info"><strong>' +
( ( _ _t = ( label _occ ) ) == null ? '' : _ _t ) +
'</strong> ' +
( ( _ _t = ( occ ) ) == null ? '' : _ _t ) +
'</p>\n<p class="room-info"><strong>' +
( ( _ _t = ( label _features ) ) == null ? '' : _ _t ) +
'</strong>\n <ul>\n ' ;
if ( passwordprotected ) {
_ _p += '\n <li class="room-info locked">' +
( ( _ _t = ( label _requires _auth ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( hidden ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _hidden ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( membersonly ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _requires _invite ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( moderated ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _moderated ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( nonanonymous ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _non _anon ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( open ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _open _room ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( persistent ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _permanent _room ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( publicroom ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _public ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( semianonymous ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _semi _anon ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( temporary ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _temp _room ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n ' ;
if ( unmoderated ) {
_ _p += '\n <li class="room-info">' +
( ( _ _t = ( label _unmoderated ) ) == null ? '' : _ _t ) +
'</li>\n ' ;
}
_ _p += '\n </ul>\n</p>\n</div>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!room_item' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<dd class="available-chatroom">\n<a class="open-room" data-room-jid="' +
( ( _ _t = ( jid ) ) == null ? '' : _ _t ) +
'"\n title="' +
( ( _ _t = ( open _title ) ) == null ? '' : _ _t ) +
'" href="#">' +
( ( _ _t = ( _ . escape ( name ) ) ) == null ? '' : _ _t ) +
'</a>\n<a class="room-info icon-room-info" data-room-jid="' +
( ( _ _t = ( jid ) ) == null ? '' : _ _t ) +
'"\n title="' +
( ( _ _t = ( info _title ) ) == null ? '' : _ _t ) +
'" href="#"> </a>\n</dd>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
define ( 'tpl!room_panel' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '<form class="pure-form pure-form-stacked converse-form add-chatroom" action="" method="post">\n <fieldset>\n <label>' +
( ( _ _t = ( label _room _name ) ) == null ? '' : _ _t ) +
'</label>\n <input type="text" name="chatroom" class="new-chatroom-name" placeholder="' +
( ( _ _t = ( label _room _name ) ) == null ? '' : _ _t ) +
2016-07-28 18:06:31 +02:00
'"/>\n ' ;
2016-02-28 10:00:22 +01:00
if ( server _input _type != 'hidden' ) {
_ _p += '\n <label' +
( ( _ _t = ( server _label _global _attr ) ) == null ? '' : _ _t ) +
'>' +
( ( _ _t = ( label _server ) ) == null ? '' : _ _t ) +
'</label>\n ' ;
}
_ _p += '\n <input type="' +
( ( _ _t = ( server _input _type ) ) == null ? '' : _ _t ) +
'" name="server" class="new-chatroom-server" placeholder="' +
( ( _ _t = ( label _server ) ) == null ? '' : _ _t ) +
2016-07-28 18:06:31 +02:00
'"/>\n <input type="submit" class="pure-button button-primary" name="join" value="' +
( ( _ _t = ( label _join ) ) == null ? '' : _ _t ) +
2016-02-28 10:00:22 +01:00
'"/>\n <input type="button" class="pure-button button-secondary" name="show" id="show-rooms" value="' +
( ( _ _t = ( label _show _rooms ) ) == null ? '' : _ _t ) +
'"/>\n </fieldset>\n</form>\n<dl id="available-chatrooms"></dl>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!roster' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-05-03 17:37:10 +02:00
_ _p += '<form class="pure-form roster-filter-group input-button-group">\n <input value="' +
( ( _ _t = ( filter _text ) ) == null ? '' : _ _t ) +
'" class="roster-filter"\n placeholder="' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( placeholder ) ) == null ? '' : _ _t ) +
2016-05-03 17:37:10 +02:00
'"\n ' ;
if ( filter _type === 'state' ) {
_ _p += ' style="display: none" ' ;
}
_ _p += ' >\n <select class="state-type" ' ;
if ( filter _type !== 'state' ) {
_ _p += ' style="display: none" ' ;
}
_ _p += ' >\n <option value="">' +
( ( _ _t = ( label _any ) ) == null ? '' : _ _t ) +
'</option>\n <option ' ;
if ( chat _state === 'online' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="online">' +
( ( _ _t = ( label _online ) ) == null ? '' : _ _t ) +
'</option>\n <option ' ;
2016-09-16 14:35:02 +02:00
if ( chat _state === 'chat' ) {
2016-05-03 17:37:10 +02:00
_ _p += ' selected="selected" ' ;
}
2016-09-16 14:35:02 +02:00
_ _p += '\n value="chat">' +
2016-05-03 17:37:10 +02:00
( ( _ _t = ( label _chatty ) ) == null ? '' : _ _t ) +
'</option>\n <option ' ;
if ( chat _state === 'dnd' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="dnd">' +
( ( _ _t = ( label _busy ) ) == null ? '' : _ _t ) +
'</option>\n <option ' ;
if ( chat _state === 'away' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="away">' +
( ( _ _t = ( label _away ) ) == null ? '' : _ _t ) +
'</option>\n <option ' ;
if ( chat _state === 'xa' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="xa">' +
( ( _ _t = ( label _xa ) ) == null ? '' : _ _t ) +
'</option>\n <option ' ;
if ( chat _state === 'offline' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="offline">' +
( ( _ _t = ( label _offline ) ) == null ? '' : _ _t ) +
'</option>\n </select>\n <select class="filter-type">\n <option ' ;
if ( filter _type === 'contacts' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="contacts">' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( label _contacts ) ) == null ? '' : _ _t ) +
2016-05-03 17:37:10 +02:00
'</option>\n <option ' ;
if ( filter _type === 'groups' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="groups">' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( label _groups ) ) == null ? '' : _ _t ) +
2016-05-03 17:37:10 +02:00
'</option>\n <option ' ;
if ( filter _type === 'state' ) {
_ _p += ' selected="selected" ' ;
}
_ _p += '\n value="state">' +
( ( _ _t = ( label _state ) ) == null ? '' : _ _t ) +
2016-02-28 10:00:22 +01:00
'</option>\n </select>\n</form>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!roster_item' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '<a class="open-chat" title="' +
( ( _ _t = ( title _fullname ) ) == null ? '' : _ _t ) +
': ' +
( ( _ _t = ( fullname ) ) == null ? '' : _ _t ) +
'\nJID: ' +
( ( _ _t = ( jid ) ) == null ? '' : _ _t ) +
'\n' +
( ( _ _t = ( desc _chat ) ) == null ? '' : _ _t ) +
'" href="#"><span class="icon-' +
( ( _ _t = ( chat _status ) ) == null ? '' : _ _t ) +
'" title="' +
( ( _ _t = ( desc _status ) ) == null ? '' : _ _t ) +
'"></span>' +
( ( _ _t = ( fullname ) ) == null ? '' : _ _t ) +
'</a>\n' ;
if ( allow _contact _removal ) {
_ _p += '\n<a class="remove-xmpp-contact icon-remove" title="' +
( ( _ _t = ( desc _remove ) ) == null ? '' : _ _t ) +
'" href="#"></a>\n' ;
}
_ _p += '\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!search_contact' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '<li>\n <form class="search-xmpp-contact">\n <input type="text"\n name="identifier"\n class="username"\n placeholder="' +
( ( _ _t = ( label _contact _name ) ) == null ? '' : _ _t ) +
'"/>\n <button type="submit">' +
( ( _ _t = ( label _search ) ) == null ? '' : _ _t ) +
'</button>\n </form>\n</li>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!select_option' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '<option value="' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'" ' ;
if ( selected ) {
_ _p += ' selected="selected" ' ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
_ _p += ' >' +
( ( _ _t = ( label ) ) == null ? '' : _ _t ) +
'</option>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!status_option' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '<li>\n <a href="#" class="' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'" data-value="' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'">\n <span class="icon-' +
( ( _ _t = ( value ) ) == null ? '' : _ _t ) +
'"></span>\n ' +
( ( _ _t = ( text ) ) == null ? '' : _ _t ) +
'\n </a>\n</li>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!toggle_chats' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '' +
( ( _ _t = ( Minimized ) ) == null ? '' : _ _t ) +
' <span id="minimized-count">(' +
( ( _ _t = ( num _minimized ) ) == null ? '' : _ _t ) +
')</span>\n<span class="unread-message-count"\n ' ;
if ( ! num _unread ) {
_ _p += ' style="display: none" ' ;
}
_ _p += '\n href="#">' +
( ( _ _t = ( num _unread ) ) == null ? '' : _ _t ) +
'</span>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!toolbar' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '' ;
if ( show _emoticons ) {
_ _p += '\n <li class="toggle-smiley icon-happy" title="' +
( ( _ _t = ( label _insert _smiley ) ) == null ? '' : _ _t ) +
'">\n <ul>\n <li><a class="icon-smiley" href="#" data-emoticon=":)"></a></li>\n <li><a class="icon-wink" href="#" data-emoticon=";)"></a></li>\n <li><a class="icon-grin" href="#" data-emoticon=":D"></a></li>\n <li><a class="icon-tongue" href="#" data-emoticon=":P"></a></li>\n <li><a class="icon-cool" href="#" data-emoticon="8)"></a></li>\n <li><a class="icon-evil" href="#" data-emoticon=">:)"></a></li>\n <li><a class="icon-confused" href="#" data-emoticon=":S"></a></li>\n <li><a class="icon-wondering" href="#" data-emoticon=":\\"></a></li>\n <li><a class="icon-angry" href="#" data-emoticon=">:("></a></li>\n <li><a class="icon-sad" href="#" data-emoticon=":("></a></li>\n <li><a class="icon-shocked" href="#" data-emoticon=":O"></a></li>\n <li><a class="icon-thumbs-up" href="#" data-emoticon="(^.^)b"></a></li>\n <li><a class="icon-heart" href="#" data-emoticon="<3"></a></li>\n </ul>\n </li>\n' ;
}
_ _p += '\n' ;
if ( show _call _button ) {
_ _p += '\n<li class="toggle-call"><a class="icon-phone" title="' +
( ( _ _t = ( label _start _call ) ) == null ? '' : _ _t ) +
'"></a></li>\n' ;
}
_ _p += '\n' ;
if ( show _occupants _toggle ) {
_ _p += '\n<li class="toggle-occupants"><a class="icon-hide-users" title="' +
( ( _ _t = ( label _hide _occupants ) ) == null ? '' : _ _t ) +
'"></a></li>\n' ;
}
_ _p += '\n' ;
if ( show _clear _button ) {
_ _p += '\n<li class="toggle-clear"><a class="icon-remove" title="' +
( ( _ _t = ( label _clear ) ) == null ? '' : _ _t ) +
'"></a></li>\n' ;
}
_ _p += '\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!toolbar_otr' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '' ;
if ( allow _otr ) {
_ _p += '\n <li class="toggle-otr ' +
( ( _ _t = ( otr _status _class ) ) == null ? '' : _ _t ) +
'" title="' +
( ( _ _t = ( otr _tooltip ) ) == null ? '' : _ _t ) +
'">\n <span class="chat-toolbar-text">' +
( ( _ _t = ( otr _translated _status ) ) == null ? '' : _ _t ) +
'</span>\n ' ;
if ( otr _status == UNENCRYPTED ) {
_ _p += '\n <span class="icon-unlocked"></span>\n ' ;
}
_ _p += '\n ' ;
if ( otr _status == UNVERIFIED ) {
_ _p += '\n <span class="icon-lock"></span>\n ' ;
}
_ _p += '\n ' ;
if ( otr _status == VERIFIED ) {
_ _p += '\n <span class="icon-lock"></span>\n ' ;
}
_ _p += '\n ' ;
if ( otr _status == FINISHED ) {
_ _p += '\n <span class="icon-unlocked"></span>\n ' ;
}
_ _p += '\n <ul>\n ' ;
if ( otr _status == UNENCRYPTED ) {
_ _p += '\n <li><a class="start-otr" href="#">' +
( ( _ _t = ( label _start _encrypted _conversation ) ) == null ? '' : _ _t ) +
'</a></li>\n ' ;
}
_ _p += '\n ' ;
if ( otr _status != UNENCRYPTED ) {
_ _p += '\n <li><a class="start-otr" href="#">' +
( ( _ _t = ( label _refresh _encrypted _conversation ) ) == null ? '' : _ _t ) +
'</a></li>\n <li><a class="end-otr" href="#">' +
( ( _ _t = ( label _end _encrypted _conversation ) ) == null ? '' : _ _t ) +
'</a></li>\n <li><a class="auth-otr" data-scheme="smp" href="#">' +
( ( _ _t = ( label _verify _with _smp ) ) == null ? '' : _ _t ) +
'</a></li>\n ' ;
}
_ _p += '\n ' ;
if ( otr _status == UNVERIFIED ) {
_ _p += '\n <li><a class="auth-otr" data-scheme="fingerprint" href="#">' +
( ( _ _t = ( label _verify _with _fingerprints ) ) == null ? '' : _ _t ) +
'</a></li>\n ' ;
}
2016-03-16 12:49:35 +01:00
_ _p += '\n <li><a href="http://www.cypherpunks.ca/otr/help/3.2.0/levels.php" target="_blank" rel="noopener">' +
2016-02-28 10:00:22 +01:00
( ( _ _t = ( label _whats _this ) ) == null ? '' : _ _t ) +
'</a></li>\n </ul>\n </li>\n' ;
}
_ _p += '\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!trimmed_chat' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '<a class="chatbox-btn close-chatbox-button icon-close"></a>\n<a class="chat-head-message-count" \n ' ;
if ( ! num _unread ) {
_ _p += ' style="display: none" ' ;
}
_ _p += '\n href="#">' +
( ( _ _t = ( num _unread ) ) == null ? '' : _ _t ) +
'</a>\n<a href="#" class="restore-chat" title="' +
( ( _ _t = ( tooltip ) ) == null ? '' : _ _t ) +
'">\n ' +
( ( _ _t = ( title ) ) == null ? '' : _ _t ) +
'\n</a>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-02-28 10:00:22 +01:00
define ( 'tpl!vcard' , [ ] , function ( ) { return function ( obj ) {
2016-02-26 18:28:18 +01:00
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
2016-02-28 10:00:22 +01:00
_ _p += '<form class="pure-form converse-form vcard-info">\n <fieldset>\n <legend>The VCard info gets rendered here</legend>\n <label>Full name:</label>\n ' +
( ( _ _t = ( fullname ) ) == null ? '' : _ _t ) +
'\n <label>URL:</label>\n ' +
( ( _ _t = ( url ) ) == null ? '' : _ _t ) +
'\n </fieldset>\n <fieldset>\n <input type="button" class="pure-button button-cancel" value="' +
( ( _ _t = ( label _return ) ) == null ? '' : _ _t ) +
'"/>\n </fieldset>\n</form>\n' ;
2016-02-26 18:28:18 +01:00
}
return _ _p ;
} ; } ) ;
2016-04-01 15:33:14 +02:00
define ( 'tpl!chatbox_minimize' , [ ] , function ( ) { return function ( obj ) {
var _ _t , _ _p = '' , _ _j = Array . prototype . join , print = function ( ) { _ _p += _ _j . call ( arguments , '' ) ; } ;
with ( obj || { } ) {
_ _p += '<a class="chatbox-btn toggle-chatbox-button icon-minus" title="' +
( ( _ _t = ( info _minimize ) ) == null ? '' : _ _t ) +
'"></a>\n' ;
}
return _ _p ;
} ; } ) ;
2016-02-26 18:28:18 +01:00
define ( "converse-templates" , [
"tpl!action" ,
"tpl!add_contact_dropdown" ,
"tpl!add_contact_form" ,
"tpl!change_status_message" ,
"tpl!chat_status" ,
"tpl!chatarea" ,
"tpl!chatbox" ,
"tpl!chatroom" ,
"tpl!chatroom_form" ,
"tpl!chatroom_password_form" ,
2016-07-28 18:06:31 +02:00
"tpl!chatroom_nickname_form" ,
2016-02-26 18:28:18 +01:00
"tpl!chatroom_sidebar" ,
"tpl!chatrooms_tab" ,
"tpl!chats_panel" ,
"tpl!choose_status" ,
"tpl!contacts_panel" ,
"tpl!contacts_tab" ,
"tpl!controlbox" ,
"tpl!controlbox_toggle" ,
"tpl!field" ,
"tpl!form_captcha" ,
"tpl!form_checkbox" ,
"tpl!form_input" ,
"tpl!form_select" ,
"tpl!form_textarea" ,
"tpl!form_username" ,
"tpl!group_header" ,
"tpl!info" ,
"tpl!login_panel" ,
"tpl!login_tab" ,
"tpl!message" ,
"tpl!new_day" ,
"tpl!occupant" ,
"tpl!pending_contact" ,
"tpl!pending_contacts" ,
"tpl!register_panel" ,
"tpl!register_tab" ,
"tpl!registration_form" ,
"tpl!registration_request" ,
"tpl!requesting_contact" ,
"tpl!requesting_contacts" ,
"tpl!room_description" ,
"tpl!room_item" ,
"tpl!room_panel" ,
"tpl!roster" ,
"tpl!roster_item" ,
"tpl!search_contact" ,
"tpl!select_option" ,
"tpl!status_option" ,
"tpl!toggle_chats" ,
"tpl!toolbar" ,
"tpl!toolbar_otr" ,
"tpl!trimmed_chat" ,
"tpl!vcard" ,
2016-04-01 15:33:14 +02:00
// Can be removed together with converse-minimize.js
// if minimization/trimming features not needed (for example for mobile
// apps).
"tpl!chatbox_minimize" ,
2016-02-26 18:28:18 +01:00
] , function ( ) {
return {
action : arguments [ 0 ] ,
add _contact _dropdown : arguments [ 1 ] ,
add _contact _form : arguments [ 2 ] ,
change _status _message : arguments [ 3 ] ,
chat _status : arguments [ 4 ] ,
chatarea : arguments [ 5 ] ,
chatbox : arguments [ 6 ] ,
chatroom : arguments [ 7 ] ,
chatroom _form : arguments [ 8 ] ,
chatroom _password _form : arguments [ 9 ] ,
2016-07-28 18:06:31 +02:00
chatroom _nickname _form : arguments [ 10 ] ,
chatroom _sidebar : arguments [ 11 ] ,
chatrooms _tab : arguments [ 12 ] ,
chats _panel : arguments [ 13 ] ,
choose _status : arguments [ 14 ] ,
contacts _panel : arguments [ 15 ] ,
contacts _tab : arguments [ 16 ] ,
controlbox : arguments [ 17 ] ,
controlbox _toggle : arguments [ 18 ] ,
field : arguments [ 19 ] ,
form _captcha : arguments [ 20 ] ,
form _checkbox : arguments [ 21 ] ,
form _input : arguments [ 22 ] ,
form _select : arguments [ 23 ] ,
form _textarea : arguments [ 24 ] ,
form _username : arguments [ 25 ] ,
group _header : arguments [ 26 ] ,
info : arguments [ 27 ] ,
login _panel : arguments [ 28 ] ,
login _tab : arguments [ 29 ] ,
message : arguments [ 30 ] ,
new _day : arguments [ 31 ] ,
occupant : arguments [ 32 ] ,
pending _contact : arguments [ 33 ] ,
pending _contacts : arguments [ 34 ] ,
register _panel : arguments [ 35 ] ,
register _tab : arguments [ 36 ] ,
registration _form : arguments [ 37 ] ,
registration _request : arguments [ 38 ] ,
requesting _contact : arguments [ 39 ] ,
requesting _contacts : arguments [ 40 ] ,
room _description : arguments [ 41 ] ,
room _item : arguments [ 42 ] ,
room _panel : arguments [ 43 ] ,
roster : arguments [ 44 ] ,
roster _item : arguments [ 45 ] ,
search _contact : arguments [ 46 ] ,
select _option : arguments [ 47 ] ,
status _option : arguments [ 48 ] ,
toggle _chats : arguments [ 49 ] ,
toolbar : arguments [ 50 ] ,
toolbar _otr : arguments [ 51 ] ,
trimmed _chat : arguments [ 52 ] ,
vcard : arguments [ 53 ] ,
chatbox _minimize : arguments [ 54 ]
2016-02-26 18:28:18 +01:00
} ;
} ) ;
2016-02-28 10:00:22 +01:00
/*global escape, locales, Jed */
( function ( root , factory ) {
define ( 'utils' , [
"jquery" ,
"jquery.browser" ,
"underscore" ,
"converse-templates"
] , factory ) ;
} ( this , function ( $ , dummy , _ , templates ) {
"use strict" ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
var XFORM _TYPE _MAP = {
'text-private' : 'password' ,
'text-single' : 'text' ,
'fixed' : 'label' ,
'boolean' : 'checkbox' ,
'hidden' : 'hidden' ,
'jid-multi' : 'textarea' ,
'list-single' : 'dropdown' ,
'list-multi' : 'dropdown'
} ;
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
var isImage = function ( url ) {
var deferred = new $ . Deferred ( ) ;
$ ( "<img>" , {
src : url ,
error : deferred . reject ,
load : deferred . resolve
} ) ;
return deferred . promise ( ) ;
} ;
2016-02-28 10:00:22 +01:00
$ . expr [ ':' ] . emptyVal = function ( obj ) {
return obj . value === '' ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
$ . fn . hasScrollBar = function ( ) {
if ( ! $ . contains ( document , this . get ( 0 ) ) ) {
return false ;
}
if ( this . parent ( ) . height ( ) < this . get ( 0 ) . scrollHeight ) {
return true ;
}
return false ;
} ;
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
$ . fn . throttledHTML = _ . throttle ( $ . fn . html , 500 ) ;
2016-02-28 10:00:22 +01:00
$ . fn . addHyperlinks = function ( ) {
if ( this . length > 0 ) {
this . each ( function ( i , obj ) {
2016-06-20 21:11:43 +02:00
var prot , escaped _url ;
var $obj = $ ( obj ) ;
var x = $obj . html ( ) ;
2016-02-28 10:00:22 +01:00
var list = x . match ( /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<]{2,200}\b/g ) ;
if ( list ) {
for ( i = 0 ; i < list . length ; i ++ ) {
2016-06-20 21:11:43 +02:00
prot = list [ i ] . indexOf ( 'http://' ) === 0 || list [ i ] . indexOf ( 'https://' ) === 0 ? '' : 'http://' ;
escaped _url = encodeURI ( decodeURI ( list [ i ] ) ) . replace ( /[!'()]/g , escape ) . replace ( /\*/g , "%2A" ) ;
2016-03-16 12:49:35 +01:00
x = x . replace ( list [ i ] , '<a target="_blank" rel="noopener" href="' + prot + escaped _url + '">' + list [ i ] + '</a>' ) ;
2016-02-28 10:00:22 +01:00
}
}
2016-06-20 21:11:43 +02:00
$obj . html ( x ) ;
_ . each ( list , function ( url ) {
isImage ( url ) . then ( function ( ) {
var prot = url . indexOf ( 'http://' ) === 0 || url . indexOf ( 'https://' ) === 0 ? '' : 'http://' ;
var escaped _url = encodeURI ( decodeURI ( url ) ) . replace ( /[!'()]/g , escape ) . replace ( /\*/g , "%2A" ) ;
var new _url = '<a target="_blank" rel="noopener" href="' + prot + escaped _url + '">' + url + '</a>' ;
event . target . className = 'chat-image' ;
x = x . replace ( new _url , event . target . outerHTML ) ;
$obj . throttledHTML ( x ) ;
} ) ;
} ) ;
2016-02-28 10:00:22 +01:00
} ) ;
}
return this ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
$ . fn . addEmoticons = function ( allowed ) {
if ( allowed ) {
if ( this . length > 0 ) {
this . each ( function ( i , obj ) {
var text = $ ( obj ) . html ( ) ;
text = text . replace ( />:\)/g , '<span class="emoticon icon-evil"></span>' ) ;
text = text . replace ( /:\)/g , '<span class="emoticon icon-smiley"></span>' ) ;
text = text . replace ( /:\-\)/g , '<span class="emoticon icon-smiley"></span>' ) ;
text = text . replace ( /;\)/g , '<span class="emoticon icon-wink"></span>' ) ;
text = text . replace ( /;\-\)/g , '<span class="emoticon icon-wink"></span>' ) ;
text = text . replace ( /:D/g , '<span class="emoticon icon-grin"></span>' ) ;
text = text . replace ( /:\-D/g , '<span class="emoticon icon-grin"></span>' ) ;
text = text . replace ( /:P/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /:\-P/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /:p/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /:\-p/g , '<span class="emoticon icon-tongue"></span>' ) ;
text = text . replace ( /8\)/g , '<span class="emoticon icon-cool"></span>' ) ;
text = text . replace ( /:S/g , '<span class="emoticon icon-confused"></span>' ) ;
text = text . replace ( /:\\/g , '<span class="emoticon icon-wondering"></span>' ) ;
text = text . replace ( /:\/ /g , '<span class="emoticon icon-wondering"></span>' ) ;
text = text . replace ( />:\(/g , '<span class="emoticon icon-angry"></span>' ) ;
text = text . replace ( /:\(/g , '<span class="emoticon icon-sad"></span>' ) ;
text = text . replace ( /:\-\(/g , '<span class="emoticon icon-sad"></span>' ) ;
text = text . replace ( /:O/g , '<span class="emoticon icon-shocked"></span>' ) ;
text = text . replace ( /:\-O/g , '<span class="emoticon icon-shocked"></span>' ) ;
text = text . replace ( /\=\-O/g , '<span class="emoticon icon-shocked"></span>' ) ;
text = text . replace ( /\(\^.\^\)b/g , '<span class="emoticon icon-thumbs-up"></span>' ) ;
text = text . replace ( /<3/g , '<span class="emoticon icon-heart"></span>' ) ;
$ ( obj ) . html ( text ) ;
} ) ;
}
}
return this ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
var utils = {
// Translation machinery
// ---------------------
_ _ : function ( str ) {
if ( typeof Jed === "undefined" ) {
return str ;
}
// FIXME: this can be refactored to take the i18n obj as a
// parameter.
// Translation factory
if ( typeof this . i18n === "undefined" ) {
this . i18n = locales . en ;
}
if ( typeof this . i18n === "string" ) {
this . i18n = $ . parseJSON ( this . i18n ) ;
}
if ( typeof this . jed === "undefined" ) {
this . jed = new Jed ( this . i18n ) ;
}
var t = this . jed . translate ( str ) ;
if ( arguments . length > 1 ) {
return t . fetch . apply ( t , [ ] . slice . call ( arguments , 1 ) ) ;
} else {
return t . fetch ( ) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
_ _ _ : 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 in src / converse - muc . js
* /
return str ;
} ,
2016-02-26 18:28:18 +01:00
2016-07-26 08:00:30 +02:00
isLocaleAvailable : function ( locale , available ) {
/ * C h e c k w h e t h e r t h e l o c a l e o r s u b l o c a l e ( e . g . e n - U S , e n ) i s s u p p o r t e d .
*
* Parameters :
* ( Function ) available - returns a boolean indicating whether the locale is supported
* /
if ( available ( locale ) ) {
return locale ;
} else {
var sublocale = locale . split ( "-" ) [ 0 ] ;
if ( sublocale !== locale && available ( sublocale ) ) {
return sublocale ;
}
}
} ,
detectLocale : function ( library _check ) {
/ * D e t e r m i n e w h i c h l o c a l e i s s u p p o r t e d b y t h e u s e r ' s s y s t e m a s w e l l
* as by the relevant library ( e . g . converse . js or moment . js ) .
*
* Parameters :
* ( Function ) library _check - returns a boolean indicating whether the locale is supported
* /
var locale , i ;
if ( window . navigator . userLanguage ) {
locale = utils . isLocaleAvailable ( window . navigator . userLanguage , library _check ) ;
}
if ( window . navigator . languages && ! locale ) {
for ( i = 0 ; i < window . navigator . languages . length && ! locale ; i ++ ) {
locale = utils . isLocaleAvailable ( window . navigator . languages [ i ] , library _check ) ;
}
}
if ( window . navigator . browserLanguage && ! locale ) {
locale = utils . isLocaleAvailable ( window . navigator . browserLanguage , library _check ) ;
}
if ( window . navigator . language && ! locale ) {
locale = utils . isLocaleAvailable ( window . navigator . language , library _check ) ;
}
if ( window . navigator . systemLanguage && ! locale ) {
locale = utils . isLocaleAvailable ( window . navigator . systemLanguage , library _check ) ;
}
return locale || 'en' ;
} ,
2016-06-20 21:11:43 +02:00
isOTRMessage : function ( message ) {
var $body = $ ( message ) . children ( 'body' ) ,
text = ( $body . length > 0 ? $body . text ( ) : undefined ) ;
return text && ! ! text . match ( /^\?OTR/ ) ;
} ,
2016-03-29 08:56:13 +02:00
isHeadlineMessage : function ( message ) {
var $message = $ ( message ) ,
from _jid = $message . attr ( 'from' ) ;
if ( $message . attr ( 'type' ) === 'headline' ||
2016-04-01 15:33:14 +02:00
// Some servers (I'm looking at you Prosody) don't set the message
// type to "headline" when sending server messages. For now we
// check if an @ signal is included, and if not, we assume it's
// a headline message.
( $message . attr ( 'type' ) !== 'error' &&
typeof from _jid !== 'undefined' &&
from _jid . indexOf ( '@' ) === - 1
) ) {
2016-03-29 08:56:13 +02:00
return true ;
}
return false ;
} ,
2016-02-28 10:00:22 +01:00
refreshWebkit : function ( ) {
/ * T h i s w o r k s a r o u n d a w e b k i t b u g . R e f r e s h e s t h e b r o w s e r ' s v i e w p o r t ,
* otherwise chatboxes are not moved along when one is closed .
* /
if ( $ . browser . webkit ) {
var conversejs = document . getElementById ( 'conversejs' ) ;
conversejs . style . display = 'none' ;
var tmp = conversejs . offsetHeight ; // jshint ignore:line
conversejs . style . display = 'block' ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
webForm2xForm : function ( field ) {
/ * T a k e s a n H T M L D O M a n d t u r n s i t i n t o a n X F o r m f i e l d .
*
* Parameters :
* ( DOMElement ) field - the field to convert
* /
var $input = $ ( field ) , value ;
if ( $input . is ( '[type=checkbox]' ) ) {
value = $input . is ( ':checked' ) && 1 || 0 ;
} else if ( $input . is ( 'textarea' ) ) {
value = [ ] ;
var lines = $input . val ( ) . split ( '\n' ) ;
for ( var vk = 0 ; vk < lines . length ; vk ++ ) {
var val = $ . trim ( lines [ vk ] ) ;
if ( val === '' )
continue ;
value . push ( val ) ;
}
} else {
value = $input . val ( ) ;
}
return $ ( templates . field ( {
name : $input . attr ( 'name' ) ,
value : value
} ) ) [ 0 ] ;
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
contains : function ( attr , query ) {
return function ( item ) {
if ( typeof attr === 'object' ) {
var value = false ;
_ . each ( attr , function ( a ) {
value = value || item . get ( a ) . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) !== - 1 ;
} ) ;
return value ;
} else if ( typeof attr === 'string' ) {
return item . get ( attr ) . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) !== - 1 ;
} else {
throw new TypeError ( 'contains: wrong attribute type. Must be string or array.' ) ;
}
} ;
} ,
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
xForm2webForm : function ( $field , $stanza ) {
/ * T a k e s a f i e l d i n X M P P X F o r m ( X E P - 0 0 4 : D a t a F o r m s ) f o r m a t
* and turns it into a HTML DOM field .
*
* Parameters :
* ( XMLElement ) field - the field to convert
* /
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// FIXME: take <required> into consideration
var options = [ ] , j , $options , $values , value , values ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( $field . attr ( 'type' ) === 'list-single' || $field . attr ( 'type' ) === 'list-multi' ) {
values = [ ] ;
$values = $field . children ( 'value' ) ;
for ( j = 0 ; j < $values . length ; j ++ ) {
values . push ( $ ( $values [ j ] ) . text ( ) ) ;
}
$options = $field . children ( 'option' ) ;
for ( j = 0 ; j < $options . length ; j ++ ) {
value = $ ( $options [ j ] ) . find ( 'value' ) . text ( ) ;
options . push ( templates . select _option ( {
value : value ,
label : $ ( $options [ j ] ) . attr ( 'label' ) ,
selected : ( values . indexOf ( value ) >= 0 ) ,
required : $field . find ( 'required' ) . length
} ) ) ;
}
return templates . form _select ( {
name : $field . attr ( 'var' ) ,
label : $field . attr ( 'label' ) ,
options : options . join ( '' ) ,
multiple : ( $field . attr ( 'type' ) === 'list-multi' ) ,
required : $field . find ( 'required' ) . length
} ) ;
} else if ( $field . attr ( 'type' ) === 'fixed' ) {
return $ ( '<p class="form-help">' ) . text ( $field . find ( 'value' ) . text ( ) ) ;
} else if ( $field . attr ( 'type' ) === 'jid-multi' ) {
return templates . form _textarea ( {
name : $field . attr ( 'var' ) ,
label : $field . attr ( 'label' ) || '' ,
value : $field . find ( 'value' ) . text ( ) ,
required : $field . find ( 'required' ) . length
} ) ;
} else if ( $field . attr ( 'type' ) === 'boolean' ) {
return templates . form _checkbox ( {
name : $field . attr ( 'var' ) ,
type : XFORM _TYPE _MAP [ $field . attr ( 'type' ) ] ,
label : $field . attr ( 'label' ) || '' ,
checked : $field . find ( 'value' ) . text ( ) === "1" && 'checked="1"' || '' ,
required : $field . find ( 'required' ) . length
} ) ;
} else if ( $field . attr ( 'type' ) && $field . attr ( 'var' ) === 'username' ) {
return templates . form _username ( {
domain : ' @' + this . domain ,
name : $field . attr ( 'var' ) ,
type : XFORM _TYPE _MAP [ $field . attr ( 'type' ) ] ,
label : $field . attr ( 'label' ) || '' ,
value : $field . find ( 'value' ) . text ( ) ,
required : $field . find ( 'required' ) . length
} ) ;
} else if ( $field . attr ( 'type' ) ) {
return templates . form _input ( {
name : $field . attr ( 'var' ) ,
type : XFORM _TYPE _MAP [ $field . attr ( 'type' ) ] ,
label : $field . attr ( 'label' ) || '' ,
value : $field . find ( 'value' ) . text ( ) ,
required : $field . find ( 'required' ) . length
} ) ;
} else {
if ( $field . attr ( 'var' ) === 'ocr' ) { // Captcha
return _ . reduce ( _ . map ( $field . find ( 'uri' ) ,
$ . proxy ( function ( uri ) {
return templates . form _captcha ( {
label : this . $field . attr ( 'label' ) ,
name : this . $field . attr ( 'var' ) ,
data : this . $stanza . find ( 'data[cid="' + uri . textContent . replace ( /^cid:/ , '' ) + '"]' ) . text ( ) ,
type : uri . getAttribute ( 'type' ) ,
required : this . $field . find ( 'required' ) . length
} ) ;
} , { '$stanza' : $stanza , '$field' : $field } )
) ,
function ( memo , num ) { return memo + num ; } , ''
) ;
}
}
}
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
utils . contains . not = function ( attr , query ) {
return function ( item ) {
return ! ( utils . contains ( attr , query ) ( item ) ) ;
} ;
} ;
return utils ;
} ) ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( ! String . prototype . endsWith ) {
String . prototype . endsWith = function ( searchString , position ) {
var subjectString = this . toString ( ) ;
if ( position === undefined || position > subjectString . length ) {
position = subjectString . length ;
}
position -= searchString . length ;
var lastIndex = subjectString . indexOf ( searchString , position ) ;
return lastIndex !== - 1 && lastIndex === position ;
} ;
}
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
String . prototype . splitOnce = function ( delimiter ) {
var components = this . split ( delimiter ) ;
return [ components . shift ( ) , components . join ( delimiter ) ] ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( ! String . prototype . trim ) {
String . prototype . trim = function ( ) {
return this . replace ( /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g , '' ) ;
} ;
}
;
define ( "polyfill" , function ( ) { } ) ;
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
/ *
2016-08-12 22:52:21 +02:00
_ _ _ _ _ _ _ _ _ _ _
/ _ _ \ / / _ _ _ _ _ _ _ _ _ _ _ _ _ _ / / _ / / _ _ ( _ ) _ _ _ _
/ / _ / / / / / / _ _ \ / _ _ \ / _ _ / / _ _ \ / / _ \ / / _ _ _ /
/ ____/ / /_/ / /_/ / /_/ / /_/ / /_/ / / __/ / ( _ _ )
/_/ / _ / \ _ _ , _ / \ _ _ , /\__, / \ _ _ / _ / _ . _ _ _ / _ / \ _ _ _ ( _ ) _ / /____/
/____/ / _ _ _ _ / /___/
2016-06-20 21:11:43 +02:00
* /
2016-08-12 22:52:21 +02:00
2016-09-16 14:35:02 +02:00
// Pluggable.js lets you to make your Javascript code pluggable while still
2016-08-12 22:52:21 +02:00
// keeping sensitive objects and data private through closures.
2016-09-16 14:35:02 +02:00
/* Start AMD header */
2016-06-20 21:11:43 +02:00
( function ( root , factory ) {
2016-09-16 14:35:02 +02:00
if ( typeof define === 'function' && define . amd ) {
define ( "pluggable" , [ "underscore" ] , factory ) ;
} else {
window . pluggable = factory ( _ ) ;
}
} ( this , function ( _ ) {
2016-06-20 21:11:43 +02:00
"use strict" ;
2016-09-16 14:35:02 +02:00
/* End AMD header */
// The `PluginSocket` class contains the plugin architecture, and gets
// created whenever `pluggable.enable(obj);` is called on the object
// that you want to make pluggable.
// You can also see it as the thing into which the plugins are plugged.
// It takes two parameters, first, the object being made pluggable, and
// then the name by which the pluggable object may be referenced on the
// __super__ object (inside overrides).
function PluginSocket ( plugged , name ) {
this . name = name ;
2016-06-20 21:11:43 +02:00
this . plugged = plugged ;
2016-09-21 17:24:32 +02:00
this . plugged . _ _super _ _ = { } ;
2016-06-20 21:11:43 +02:00
this . plugins = { } ;
this . initialized _plugins = [ ] ;
}
2016-08-12 22:52:21 +02:00
2016-09-16 14:35:02 +02:00
// Now we add methods to the PluginSocket by adding them to its
// prototype.
_ . extend ( PluginSocket . prototype , {
2016-08-12 22:52:21 +02:00
2016-09-16 14:35:02 +02:00
// `wrappedOverride` creates a partially applied wrapper function
// that makes sure to set the proper super method when the
// overriding method is called. This is done to enable
// chaining of plugin methods, all the way up to the
// original method.
2016-06-20 21:11:43 +02:00
wrappedOverride : function ( key , value , super _method ) {
if ( typeof super _method === "function" ) {
2016-09-16 14:35:02 +02:00
if ( typeof this . _ _super _ _ === "undefined" ) {
/ * W e ' r e n o t o n t h e c o n t e x t o f t h e p l u g g e d o b j e c t .
* This can happen when the overridden method is called via
* an event handler . In this case , we simply tack on the
* _ _super _ _ obj .
* /
this . _ _super _ _ = { } ;
2016-07-26 08:00:30 +02:00
}
2016-09-16 14:35:02 +02:00
this . _ _super _ _ [ key ] = super _method . bind ( this ) ;
2016-06-20 21:11:43 +02:00
}
return value . apply ( this , _ . rest ( arguments , 3 ) ) ;
} ,
2016-09-16 14:35:02 +02:00
// `_overrideAttribute` overrides an attribute on the original object
// (the thing being plugged into).
//
// If the attribute being overridden is a function, then the original
// function will still be available via the `__super__` attribute.
//
// If the same function is being overridden multiple times, then
// the original function will be available at the end of a chain of
// functions, starting from the most recent override, all the way
// back to the original function, each being referenced by the
// previous' __super__ attribute.
//
// For example:
//
// `plugin2.MyFunc.__super__.myFunc => plugin1.MyFunc.__super__.myFunc => original.myFunc`
2016-06-20 21:11:43 +02:00
_overrideAttribute : function ( key , plugin ) {
var value = plugin . overrides [ key ] ;
if ( typeof value === "function" ) {
var wrapped _function = _ . partial (
this . wrappedOverride , key , value , this . plugged [ key ]
) ;
this . plugged [ key ] = wrapped _function ;
} else {
this . plugged [ key ] = value ;
}
} ,
_extendObject : function ( obj , attributes ) {
2016-09-16 14:35:02 +02:00
if ( ! obj . prototype . _ _super _ _ ) {
obj . prototype . _ _super _ _ = { } ;
obj . prototype . _ _super _ _ [ this . name ] = this . plugged ;
2016-06-20 21:11:43 +02:00
}
_ . each ( attributes , function ( value , key ) {
if ( key === 'events' ) {
obj . prototype [ key ] = _ . extend ( value , obj . prototype [ key ] ) ;
} else if ( typeof value === 'function' ) {
// We create a partially applied wrapper function, that
// makes sure to set the proper super method when the
// overriding method is called. This is done to enable
// chaining of plugin methods, all the way up to the
// original method.
var wrapped _function = _ . partial (
this . wrappedOverride , key , value , obj . prototype [ key ]
) ;
obj . prototype [ key ] = wrapped _function ;
} else {
obj . prototype [ key ] = value ;
}
} . bind ( this ) ) ;
} ,
2016-09-16 14:35:02 +02:00
// Plugins can specify optional dependencies (by means of the
// `optional_dependencies` list attribute) which refers to dependencies
// which will be initialized first, before the plugin itself gets initialized.
// They are optional in the sense that if they aren't available, an
// error won't be thrown.
// However, if you want to make these dependencies strict (i.e.
// non-optional), you can set the `strict_plugin_dependencies` attribute to `true`
// on the object being made pluggable (i.e. the object passed to
// `pluggable.enable`).
2016-06-20 21:11:43 +02:00
loadOptionalDependencies : function ( plugin ) {
_ . each ( plugin . optional _dependencies , function ( name ) {
var dep = this . plugins [ name ] ;
if ( dep ) {
if ( _ . contains ( dep . optional _dependencies , plugin . _ _name _ _ ) ) {
2016-08-12 22:52:21 +02:00
/* FIXME: circular dependency checking is only one level deep. */
2016-06-20 21:11:43 +02:00
throw "Found a circular dependency between the plugins \"" +
plugin . _ _name _ _ + "\" and \"" + name + "\"" ;
}
this . initializePlugin ( dep ) ;
} else {
this . throwUndefinedDependencyError (
"Could not find optional dependency \"" + name + "\" " +
"for the plugin \"" + plugin . _ _name _ _ + "\". " +
"If it's needed, make sure it's loaded by require.js" ) ;
}
} . bind ( this ) ) ;
} ,
throwUndefinedDependencyError : function ( msg ) {
if ( this . plugged . strict _plugin _dependencies ) {
throw msg ;
} else {
console . log ( msg ) ;
return ;
}
} ,
2016-09-16 14:35:02 +02:00
// `applyOverrides` is called by initializePlugin. It applies any
// and all overrides of methods or Backbone views and models that
// are defined on any of the plugins.
2016-06-20 21:11:43 +02:00
applyOverrides : function ( plugin ) {
_ . each ( Object . keys ( plugin . overrides || { } ) , function ( key ) {
var override = plugin . overrides [ key ] ;
if ( typeof override === "object" ) {
if ( typeof this . plugged [ key ] === 'undefined' ) {
2016-09-16 14:35:02 +02:00
this . throwUndefinedDependencyError (
"Error: Plugin \"" + plugin . _ _name _ _ +
"\" tried to override " + key + " but it's not found." ) ;
2016-06-20 21:11:43 +02:00
} else {
this . _extendObject ( this . plugged [ key ] , override ) ;
}
} else {
this . _overrideAttribute ( key , plugin ) ;
}
} . bind ( this ) ) ;
} ,
2016-09-16 14:35:02 +02:00
// `initializePlugin` applies the overrides (if any) defined on all
// the registered plugins and then calls the initialize method for each plugin.
2016-06-20 21:11:43 +02:00
initializePlugin : function ( plugin ) {
if ( _ . contains ( this . initialized _plugins , plugin . _ _name _ _ ) ) {
2016-09-16 14:35:02 +02:00
/ * D o n ' t i n i t i a l i z e p l u g i n s t w i c e , o t h e r w i s e w e g e t
* infinite recursion in overridden methods .
* /
2016-06-20 21:11:43 +02:00
return ;
}
_ . extend ( plugin , this . properties ) ;
if ( plugin . optional _dependencies ) {
this . loadOptionalDependencies ( plugin ) ;
}
this . applyOverrides ( plugin ) ;
if ( typeof plugin . initialize === "function" ) {
plugin . initialize . bind ( plugin ) ( this ) ;
}
this . initialized _plugins . push ( plugin . _ _name _ _ ) ;
} ,
2016-09-16 14:35:02 +02:00
// `registerPlugin` registers (or inserts, if you'd like) a plugin,
// by adding it to the `plugins` map on the PluginSocket instance.
registerPlugin : function ( name , plugin ) {
plugin . _ _name _ _ = name ;
this . plugins [ name ] = plugin ;
} ,
// `initializePlugins` should get called once all plugins have been
// registered. It will then iterate through all the plugins, calling
// `initializePlugin` for each.
// The passed in properties variable is an object with attributes and methods
// which will be attached to the plugins.
2016-06-20 21:11:43 +02:00
initializePlugins : function ( properties ) {
if ( ! _ . size ( this . plugins ) ) {
return ;
}
this . properties = properties ;
_ . each ( _ . values ( this . plugins ) , this . initializePlugin . bind ( this ) ) ;
}
} ) ;
return {
2016-09-16 14:35:02 +02:00
// Call the `enable` method to make an object pluggable
//
// It takes three parameters:
// - `object`: The object that gets made pluggable.
// - `name`: The string name by which the now pluggable object
// may be referenced on the __super__ obj (in overrides).
// The default value is "plugged".
// - `attrname`: The string name of the attribute on the now
// pluggable object, which refers to the PluginSocket instance
// that gets created.
'enable' : function ( object , name , attrname ) {
if ( typeof attrname === "undefined" ) {
attrname = "pluginSocket" ;
}
if ( typeof name === 'undefined' ) {
name = 'plugged' ;
}
var ref = { } ;
ref [ attrname ] = new PluginSocket ( object , name ) ;
return _ . extend ( object , ref ) ;
2016-06-20 21:11:43 +02:00
}
} ;
} ) ) ;
2016-02-28 10:00:22 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global Backbone, define, window, document, locales */
2016-02-26 18:28:18 +01:00
( function ( root , factory ) {
2016-02-28 10:00:22 +01:00
// Two modules are loaded as dependencies.
//
// * **converse-dependencies**: A list of dependencies converse.js depends on.
// The path to this module is in main.js and the module itself can be overridden.
// * **converse-templates**: The HTML templates used by converse.js.
//
// The dependencies are then split up and passed into the factory function,
// which contains and instantiates converse.js.
define ( "converse-core" , [
2016-02-26 18:28:18 +01:00
"jquery" ,
"underscore" ,
2016-02-28 10:00:22 +01:00
"polyfill" ,
"utils" ,
"moment_with_locales" ,
"strophe" ,
2016-02-26 18:28:18 +01:00
"converse-templates" ,
2016-08-12 22:52:21 +02:00
"pluggable" ,
2016-02-28 10:00:22 +01:00
"strophe.disco" ,
"backbone.browserStorage" ,
"backbone.overview" ,
2016-02-26 18:28:18 +01:00
] , factory ) ;
2016-06-20 21:11:43 +02:00
} ( this , function ( $ , _ , dummy , utils , moment , Strophe , templates , pluggable ) {
2016-03-16 12:49:35 +01:00
/ *
2016-02-28 10:00:22 +01:00
* Cannot use this due to Safari bug .
* See https : //github.com/jcbrand/converse.js/issues/196
* /
// "use strict";
// Strophe globals
var $build = Strophe . $build ;
var $iq = Strophe . $iq ;
var $pres = Strophe . $pres ;
var b64 _sha1 = Strophe . SHA1 . b64 _sha1 ;
Strophe = Strophe . Strophe ;
// Use Mustache style syntax for variable interpolation
/ * C o n f i g u r a t i o n o f u n d e r s c o r e t e m p l a t e s ( t h i s c o n f i g i s d i s t i n c t t o t h e
* config of requirejs - tpl in main . js ) . This one is for normal inline templates .
* /
_ . templateSettings = {
evaluate : /\{\[([\s\S]+?)\]\}/g ,
interpolate : /\{\{([\s\S]+?)\}\}/g
} ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
// We create an object to act as the "this" context for event handlers (as
// defined below and accessible via converse_api.listen).
// We don't want the inner converse object to be the context, since it
// contains sensitive information, and we don't want it to be something in
// the DOM or window, because then anyone can trigger converse events.
var event _context = { } ;
2016-02-28 10:00:22 +01:00
var converse = {
templates : templates ,
2016-06-20 21:11:43 +02:00
2016-02-28 10:00:22 +01:00
emit : function ( evt , data ) {
2016-03-07 18:54:07 +01:00
$ ( event _context ) . trigger ( evt , data ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-06-20 21:11:43 +02:00
2016-09-21 17:24:32 +02:00
once : function ( evt , handler , context ) {
if ( context ) {
handler = handler . bind ( context ) ;
}
2016-03-07 18:54:07 +01:00
$ ( event _context ) . one ( evt , handler ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-06-20 21:11:43 +02:00
2016-09-21 17:24:32 +02:00
on : function ( evt , handler , context ) {
2016-05-24 10:23:19 +02:00
if ( _ . contains ( [ 'ready' , 'initialized' ] , evt ) ) {
converse . log ( 'Warning: The "' + evt + '" event has been deprecated and will be removed, please use "connected".' ) ;
}
2016-09-21 17:24:32 +02:00
if ( context ) {
handler = handler . bind ( context ) ;
}
2016-03-07 18:54:07 +01:00
$ ( event _context ) . bind ( evt , handler ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-06-20 21:11:43 +02:00
2016-02-28 10:00:22 +01:00
off : function ( evt , handler ) {
2016-03-07 18:54:07 +01:00
$ ( event _context ) . unbind ( evt , handler ) ;
2016-02-28 10:00:22 +01:00
}
2016-02-26 18:28:18 +01:00
} ;
2016-06-20 21:11:43 +02:00
// Make converse pluggable
2016-09-16 14:35:02 +02:00
pluggable . enable ( converse , 'converse' , 'pluggable' ) ;
2016-06-20 21:11:43 +02:00
2016-02-28 10:00:22 +01:00
// Module-level constants
converse . STATUS _WEIGHTS = {
'offline' : 6 ,
'unavailable' : 5 ,
'xa' : 4 ,
'away' : 3 ,
'dnd' : 2 ,
'chat' : 1 , // We currently don't differentiate between "chat" and "online"
'online' : 1
2016-02-26 18:28:18 +01:00
} ;
2016-03-07 18:54:07 +01:00
converse . ANONYMOUS = "anonymous" ;
converse . CLOSED = 'closed' ;
2016-09-16 14:35:02 +02:00
converse . EXTERNAL = "external" ;
converse . LOGIN = "login" ;
converse . LOGOUT = "logout" ;
converse . OPENED = 'opened' ;
converse . PREBIND = "prebind" ;
2016-03-07 18:54:07 +01:00
var PRETTY _CONNECTION _STATUS = {
0 : 'ERROR' ,
1 : 'CONNECTING' ,
2 : 'CONNFAIL' ,
3 : 'AUTHENTICATING' ,
4 : 'AUTHFAIL' ,
5 : 'CONNECTED' ,
6 : 'DISCONNECTED' ,
7 : 'DISCONNECTING' ,
8 : 'ATTACHED' ,
9 : 'REDIRECT'
} ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
converse . log = function ( txt , level ) {
var logger ;
if ( typeof console === "undefined" || typeof console . log === "undefined" ) {
logger = { log : function ( ) { } , error : function ( ) { } } ;
} else {
logger = console ;
}
if ( converse . debug ) {
if ( level === 'error' ) {
logger . log ( 'ERROR: ' + txt ) ;
} else {
logger . log ( txt ) ;
}
}
2016-02-26 18:28:18 +01:00
} ;
2016-03-07 18:54:07 +01:00
2016-02-28 10:00:22 +01:00
converse . initialize = function ( settings , callback ) {
"use strict" ;
2016-06-20 21:11:43 +02:00
var init _deferred = new $ . Deferred ( ) ;
2016-02-28 10:00:22 +01:00
var converse = this ;
var unloadevent ;
if ( 'onpagehide' in window ) {
// Pagehide gets thrown in more cases than unload. Specifically it
// gets thrown when the page is cached and not just
// closed/destroyed. It's the only viable event on mobile Safari.
// https://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
unloadevent = 'pagehide' ;
} else if ( 'onbeforeunload' in window ) {
unloadevent = 'beforeunload' ;
} else if ( 'onunload' in window ) {
unloadevent = 'unload' ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
// Logging
Strophe . log = function ( level , msg ) { converse . log ( level + ' ' + msg , level ) ; } ;
Strophe . error = function ( msg ) { converse . log ( msg , 'error' ) ; } ;
// Add Strophe Namespaces
Strophe . addNamespace ( 'CARBONS' , 'urn:xmpp:carbons:2' ) ;
Strophe . addNamespace ( 'CHATSTATES' , 'http://jabber.org/protocol/chatstates' ) ;
Strophe . addNamespace ( 'CSI' , 'urn:xmpp:csi:0' ) ;
Strophe . addNamespace ( 'ROSTERX' , 'http://jabber.org/protocol/rosterx' ) ;
Strophe . addNamespace ( 'XFORM' , 'jabber:x:data' ) ;
2016-03-16 12:49:35 +01:00
Strophe . addNamespace ( 'NICK' , 'http://jabber.org/protocol/nick' ) ;
2016-06-20 21:11:43 +02:00
Strophe . addNamespace ( 'HINTS' , 'urn:xmpp:hints' ) ;
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
// Instance level constants
2016-02-28 10:00:22 +01:00
this . TIMEOUTS = { // Set as module attr so that we can override in tests.
2016-09-16 14:35:02 +02:00
'PAUSED' : 10000 ,
2016-02-28 10:00:22 +01:00
'INACTIVE' : 90000
} ;
2016-03-07 18:54:07 +01:00
// XEP-0085 Chat states
// http://xmpp.org/extensions/xep-0085.html
this . INACTIVE = 'inactive' ;
this . ACTIVE = 'active' ;
this . COMPOSING = 'composing' ;
this . PAUSED = 'paused' ;
this . GONE = 'gone' ;
2016-02-28 10:00:22 +01:00
// Detect support for the user's locale
// ------------------------------------
this . isConverseLocale = function ( locale ) { return typeof locales [ locale ] !== "undefined" ; } ;
this . isMomentLocale = function ( locale ) { return moment . locale ( ) !== moment . locale ( locale ) ; } ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . user _settings = settings ; // Save the user settings so that they can be used by plugins
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . wrappedChatBox = function ( chatbox ) {
/ * W r a p a c h a t b o x f o r o u t s i d e c o n s u m p t i o n ( i . e . s o t h a t i t c a n b e
* returned via the API .
2016-02-26 18:28:18 +01:00
* /
2016-02-28 10:00:22 +01:00
if ( ! chatbox ) { return ; }
2016-07-26 08:00:30 +02:00
var view = converse . chatboxviews . get ( chatbox . get ( 'id' ) ) ;
2016-02-28 10:00:22 +01:00
return {
'close' : view . close . bind ( view ) ,
'focus' : view . focus . bind ( view ) ,
'get' : chatbox . get . bind ( chatbox ) ,
'open' : view . show . bind ( view ) ,
'set' : chatbox . set . bind ( chatbox )
} ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
if ( ! moment . locale ) { //moment.lang is deprecated after 2.8.1, use moment.locale instead
moment . locale = moment . lang ;
}
2016-07-26 08:00:30 +02:00
moment . locale ( utils . detectLocale ( this . isMomentLocale ) ) ;
this . i18n = settings . i18n ? settings . i18n : locales [ utils . detectLocale ( this . isConverseLocale ) ] || { } ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Translation machinery
// ---------------------
var _ _ = utils . _ _ . bind ( this ) ;
2016-07-26 08:00:30 +02:00
var DESC _GROUP _TOGGLE = _ _ ( 'Click to hide these contacts' ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Default configuration values
// ----------------------------
this . default _settings = {
allow _contact _requests : true ,
animate : true ,
2016-09-16 14:35:02 +02:00
authentication : 'login' , // Available values are "login", "prebind", "anonymous" and "external".
2016-02-28 10:00:22 +01:00
auto _away : 0 , // Seconds after which user status is set to 'away'
auto _login : false , // Currently only used in connection with anonymous login
auto _reconnect : false ,
auto _subscribe : false ,
auto _xa : 0 , // Seconds after which user status is set to 'xa'
bosh _service _url : undefined , // The BOSH connection manager URL.
2016-04-01 15:33:14 +02:00
credentials _url : null , // URL from where login credentials can be fetched
2016-02-28 10:00:22 +01:00
csi _waiting _time : 0 , // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
debug : false ,
2016-06-20 21:11:43 +02:00
default _state : 'online' ,
2016-02-28 10:00:22 +01:00
expose _rid _and _sid : false ,
2016-06-20 21:11:43 +02:00
filter _by _resource : false ,
2016-02-28 10:00:22 +01:00
forward _messages : false ,
hide _offline _users : false ,
include _offline _state : false ,
jid : undefined ,
keepalive : false ,
locked _domain : undefined ,
message _carbons : false , // Support for XEP-280
password : undefined ,
prebind : false , // XXX: Deprecated, use "authentication" instead.
prebind _url : null ,
rid : undefined ,
roster _groups : false ,
show _only _online _users : false ,
sid : undefined ,
storage : 'session' ,
2016-09-16 14:35:02 +02:00
message _storage : 'session' ,
2016-03-16 12:49:35 +01:00
strict _plugin _dependencies : false ,
2016-02-28 10:00:22 +01:00
synchronize _availability : true , // Set to false to not sync with other clients or with resource name of the particular client that it should synchronize with
visible _toolbar _buttons : {
'emoticons' : true ,
'call' : false ,
'clear' : true ,
'toggle_occupants' : true
} ,
websocket _url : undefined ,
xhr _custom _status : false ,
xhr _custom _status _url : '' ,
} ;
_ . extend ( this , this . default _settings ) ;
// Allow only whitelisted configuration attributes to be overwritten
_ . extend ( this , _ . pick ( settings , Object . keys ( this . default _settings ) ) ) ;
// BBB
2016-03-07 18:54:07 +01:00
if ( this . prebind === true ) { this . authentication = converse . PREBIND ; }
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
if ( this . authentication === converse . ANONYMOUS ) {
2016-07-26 08:00:30 +02:00
if ( this . auto _login && ! this . jid ) {
throw new Error ( "Config Error: you need to provide the server's " +
"domain via the 'jid' option when using anonymous " +
"authentication with auto_login." ) ;
2016-02-26 18:28:18 +01:00
}
}
2016-02-28 10:00:22 +01:00
if ( settings . visible _toolbar _buttons ) {
_ . extend (
this . visible _toolbar _buttons ,
_ . pick ( settings . visible _toolbar _buttons , [
'emoticons' , 'call' , 'clear' , 'toggle_occupants'
]
) ) ;
}
$ . fx . off = ! this . animate ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Module-level variables
// ----------------------
this . callback = callback || function ( ) { } ;
/ * W h e n r e l o a d i n g t h e p a g e :
* For new sessions , we need to send out a presence stanza to notify
* the server / network that we ' re online .
* When re - attaching to an existing session ( e . g . via the keepalive
* option ) , we don ' t need to again send out a presence stanza , because
* it ' s as if "we never left" ( see onConnectStatusChanged ) .
* https : //github.com/jcbrand/converse.js/issues/521
* /
this . send _initial _presence = true ;
this . msg _counter = 0 ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Module-level functions
// ----------------------
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . generateResource = function ( ) {
return '/converse.js-' + Math . floor ( Math . random ( ) * 139749825 ) . toString ( ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . sendCSI = function ( stat ) {
/* Send out a Chat Status Notification (XEP-0352) */
if ( converse . features [ Strophe . NS . CSI ] || true ) {
converse . connection . send ( $build ( stat , { xmlns : Strophe . NS . CSI } ) ) ;
2016-05-03 17:37:10 +02:00
converse . inactive = ( stat === converse . INACTIVE ) ? true : false ;
2016-02-28 10:00:22 +01:00
}
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . onUserActivity = function ( ) {
/* Resets counters and flags relating to CSI and auto_away/auto_xa */
2016-05-03 17:37:10 +02:00
if ( converse . idle _seconds > 0 ) {
converse . idle _seconds = 0 ;
2016-02-28 10:00:22 +01:00
}
if ( ! converse . connection . authenticated ) {
// We can't send out any stanzas when there's no authenticated connection.
2016-05-03 17:37:10 +02:00
// converse can happen when the connection reconnects.
2016-02-28 10:00:22 +01:00
return ;
}
2016-05-03 17:37:10 +02:00
if ( converse . inactive ) {
converse . sendCSI ( converse . ACTIVE ) ;
2016-02-28 10:00:22 +01:00
}
2016-05-03 17:37:10 +02:00
if ( converse . auto _changed _status === true ) {
converse . auto _changed _status = false ;
2016-06-20 21:11:43 +02:00
// XXX: we should really remember the original state here, and
// then set it back to that...
converse . xmppstatus . setStatus ( converse . default _state ) ;
2016-02-28 10:00:22 +01:00
}
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . onEverySecond = function ( ) {
/ * A n i n t e r v a l h a n d l e r r u n n i n g e v e r y s e c o n d .
2016-05-03 17:37:10 +02:00
* Used for CSI and the auto _away and auto _xa features .
2016-02-28 10:00:22 +01:00
* /
if ( ! converse . connection . authenticated ) {
// We can't send out any stanzas when there's no authenticated connection.
// This can happen when the connection reconnects.
return ;
}
2016-05-03 17:37:10 +02:00
var stat = converse . xmppstatus . getStatus ( ) ;
converse . idle _seconds ++ ;
if ( converse . csi _waiting _time > 0 &&
converse . idle _seconds > converse . csi _waiting _time &&
! converse . inactive ) {
converse . sendCSI ( converse . INACTIVE ) ;
2016-02-28 10:00:22 +01:00
}
2016-05-03 17:37:10 +02:00
if ( converse . auto _away > 0 &&
converse . idle _seconds > converse . auto _away &&
stat !== 'away' && stat !== 'xa' ) {
converse . auto _changed _status = true ;
converse . xmppstatus . setStatus ( 'away' ) ;
} else if ( converse . auto _xa > 0 &&
converse . idle _seconds > converse . auto _xa && stat !== 'xa' ) {
converse . auto _changed _status = true ;
converse . xmppstatus . setStatus ( 'xa' ) ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . registerIntervalHandler = function ( ) {
/ * S e t a n i n t e r v a l o f o n e s e c o n d a n d r e g i s t e r a h a n d l e r f o r i t .
* Required for the auto _away , auto _xa and csi _waiting _time features .
* /
2016-05-03 17:37:10 +02:00
if ( converse . auto _away < 1 && converse . auto _xa < 1 && converse . csi _waiting _time < 1 ) {
2016-02-28 10:00:22 +01:00
// Waiting time of less then one second means features aren't used.
return ;
2016-02-26 18:28:18 +01:00
}
2016-05-03 17:37:10 +02:00
converse . idle _seconds = 0 ;
converse . auto _changed _status = false ; // Was the user's status changed by converse.js?
$ ( window ) . on ( 'click mousemove keypress focus' + unloadevent , converse . onUserActivity ) ;
converse . everySecondTrigger = window . setInterval ( converse . onEverySecond , 1000 ) ;
2016-02-28 10:00:22 +01:00
} ;
2016-09-16 14:35:02 +02:00
this . giveFeedback = function ( subject , klass , message ) {
2016-02-28 10:00:22 +01:00
$ ( '.conn-feedback' ) . each ( function ( idx , el ) {
var $el = $ ( el ) ;
2016-09-16 14:35:02 +02:00
$el . addClass ( 'conn-feedback' ) . text ( subject ) ;
2016-02-28 10:00:22 +01:00
if ( klass ) {
$el . addClass ( klass ) ;
} else {
$el . removeClass ( 'error' ) ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
} ) ;
2016-09-16 14:35:02 +02:00
converse . emit ( 'feedback' , {
'klass' : klass ,
'message' : message ,
'subject' : subject
} ) ;
2016-02-28 10:00:22 +01:00
} ;
this . rejectPresenceSubscription = function ( jid , message ) {
/ * R e j e c t o r c a n c e l a n o t h e r u s e r ' s s u b s c r i p t i o n t o o u r p r e s e n c e u p d a t e s .
* Parameters :
* ( String ) jid - The Jabber ID of the user whose subscription
* is being canceled .
* ( String ) message - An optional message to the user
* /
var pres = $pres ( { to : jid , type : "unsubscribed" } ) ;
if ( message && message !== "" ) { pres . c ( "status" ) . t ( message ) ; }
converse . connection . send ( pres ) ;
2016-02-26 18:28:18 +01:00
} ;
2016-05-03 17:37:10 +02:00
this . reconnect = _ . debounce ( function ( condition ) {
2016-09-16 14:35:02 +02:00
converse . connection . reconnecting = true ;
2016-05-03 17:37:10 +02:00
converse . connection . disconnect ( 're-connecting' ) ;
converse . connection . reset ( ) ;
2016-09-16 14:35:02 +02:00
converse . log ( 'The connection has dropped, attempting to reconnect.' ) ;
converse . giveFeedback (
_ _ ( "Reconnecting" ) , 'warn' , _ _ ( 'The connection has dropped, attempting to reconnect.' )
) ;
2016-05-03 17:37:10 +02:00
converse . clearSession ( ) ;
converse . _tearDown ( ) ;
if ( converse . authentication !== "prebind" ) {
2016-09-16 14:35:02 +02:00
converse . autoLogin ( ) ;
2016-05-03 17:37:10 +02:00
} else if ( converse . prebind _url ) {
converse . startNewBOSHSession ( ) ;
}
} , 1000 ) ;
2016-04-01 15:33:14 +02:00
this . onDisconnected = function ( condition ) {
2016-09-16 14:35:02 +02:00
if ( converse . disconnection _cause !== converse . LOGOUT ) {
if ( converse . disconnection _cause === Strophe . Status . CONNFAIL && converse . auto _reconnect ) {
converse . reconnect ( condition ) ;
converse . log ( 'RECONNECTING' ) ;
return 'reconnecting' ;
} else if (
( converse . disconnection _cause === Strophe . Status . DISCONNECTING ||
converse . disconnection _cause === Strophe . Status . DISCONNECTED ) &&
converse . auto _reconnect ) {
window . setTimeout ( _ . partial ( converse . reconnect , condition ) , 3000 ) ;
converse . log ( 'RECONNECTING IN 3 SECONDS' ) ;
return 'reconnecting' ;
}
}
delete converse . connection . reconnecting ;
converse . _tearDown ( ) ;
converse . emit ( 'disconnected' ) ;
converse . log ( 'DISCONNECTED' ) ;
return 'disconnected' ;
} ;
this . setDisconnectionCause = function ( connection _status ) {
if ( typeof converse . disconnection _cause === "undefined" ) {
converse . disconnection _cause = connection _status ;
2016-04-01 15:33:14 +02:00
}
2016-02-28 10:00:22 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-09-16 14:35:02 +02:00
this . onConnectStatusChanged = function ( status , condition ) {
2016-02-28 10:00:22 +01:00
converse . log ( "Status changed to: " + PRETTY _CONNECTION _STATUS [ status ] ) ;
if ( status === Strophe . Status . CONNECTED || status === Strophe . Status . ATTACHED ) {
// By default we always want to send out an initial presence stanza.
converse . send _initial _presence = true ;
delete converse . disconnection _cause ;
2016-09-16 14:35:02 +02:00
if ( converse . connection . reconnecting ) {
2016-02-28 10:00:22 +01:00
converse . log ( status === Strophe . Status . CONNECTED ? 'Reconnected' : 'Reattached' ) ;
converse . onReconnected ( ) ;
} else {
converse . log ( status === Strophe . Status . CONNECTED ? 'Connected' : 'Attached' ) ;
if ( converse . connection . restored ) {
2016-09-16 14:35:02 +02:00
// No need to send an initial presence stanza when
// we're restoring an existing session.
converse . send _initial _presence = false ;
2016-02-28 10:00:22 +01:00
}
converse . onConnected ( ) ;
}
} else if ( status === Strophe . Status . DISCONNECTED ) {
2016-09-16 14:35:02 +02:00
converse . setDisconnectionCause ( status ) ;
2016-04-01 15:33:14 +02:00
converse . onDisconnected ( condition ) ;
2016-09-16 14:35:02 +02:00
if ( status === Strophe . Status . DISCONNECTING && condition ) {
converse . giveFeedback (
_ _ ( "Disconnected" ) , 'warn' ,
_ _ ( "The connection to the chat server has dropped" )
) ;
}
2016-02-28 10:00:22 +01:00
} else if ( status === Strophe . Status . ERROR ) {
2016-09-16 14:35:02 +02:00
converse . giveFeedback (
_ _ ( 'Connection error' ) , 'error' ,
_ _ ( 'An error occurred while connecting to the chat server.' )
) ;
2016-02-28 10:00:22 +01:00
} else if ( status === Strophe . Status . CONNECTING ) {
converse . giveFeedback ( _ _ ( 'Connecting' ) ) ;
} else if ( status === Strophe . Status . AUTHENTICATING ) {
converse . giveFeedback ( _ _ ( 'Authenticating' ) ) ;
} else if ( status === Strophe . Status . AUTHFAIL ) {
2016-09-16 14:35:02 +02:00
converse . giveFeedback ( _ _ ( 'Authentication failed.' ) , 'error' ) ;
2016-02-28 10:00:22 +01:00
converse . connection . disconnect ( _ _ ( 'Authentication Failed' ) ) ;
converse . disconnection _cause = Strophe . Status . AUTHFAIL ;
2016-09-16 14:35:02 +02:00
} else if ( status === Strophe . Status . CONNFAIL ||
status === Strophe . Status . DISCONNECTING ) {
converse . setDisconnectionCause ( status ) ;
2016-02-26 18:28:18 +01:00
}
} ;
2016-02-28 10:00:22 +01: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 + ") " ) ;
}
} else if ( document . title . search ( /^Messages \(\d+\) / ) !== - 1 ) {
document . title = document . title . replace ( /^Messages \(\d+\) / , "" ) ;
2016-02-26 18:28:18 +01:00
}
} ;
2016-02-28 10:00:22 +01:00
this . incrementMsgCounter = function ( ) {
this . msg _counter += 1 ;
this . updateMsgCounter ( ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . clearMsgCounter = function ( ) {
this . msg _counter = 0 ;
this . updateMsgCounter ( ) ;
2016-02-26 18:28:18 +01:00
} ;
2016-03-16 12:49:35 +01:00
this . initStatus = function ( ) {
var deferred = new $ . Deferred ( ) ;
2016-02-28 10:00:22 +01:00
this . xmppstatus = new this . XMPPStatus ( ) ;
var id = b64 _sha1 ( 'converse.xmppstatus-' + converse . bare _jid ) ;
this . xmppstatus . id = id ; // Appears to be necessary for backbone.browserStorage
this . xmppstatus . browserStorage = new Backbone . BrowserStorage [ converse . storage ] ( id ) ;
2016-03-16 12:49:35 +01:00
this . xmppstatus . fetch ( {
success : deferred . resolve ,
error : deferred . resolve
} ) ;
converse . emit ( 'statusInitialized' ) ;
return deferred . promise ( ) ;
2016-02-28 10:00:22 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . initSession = function ( ) {
this . session = new this . Session ( ) ;
var id = b64 _sha1 ( 'converse.bosh-session' ) ;
this . session . id = id ; // Appears to be necessary for backbone.browserStorage
this . session . browserStorage = new Backbone . BrowserStorage [ converse . storage ] ( id ) ;
this . session . fetch ( ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
this . clearSession = function ( ) {
if ( this . roster ) {
this . roster . browserStorage . _clear ( ) ;
}
this . session . browserStorage . _clear ( ) ;
2016-02-26 18:28:18 +01:00
} ;
2016-02-28 10:00:22 +01:00
this . logOut = function ( ) {
2016-09-16 14:35:02 +02:00
converse . disconnection _cause = converse . LOGOUT ;
2016-03-07 18:54:07 +01:00
converse . chatboxviews . closeAllChatBoxes ( ) ;
2016-02-28 10:00:22 +01:00
converse . clearSession ( ) ;
2016-05-03 17:37:10 +02:00
if ( typeof converse . connection !== 'undefined' ) {
converse . connection . disconnect ( ) ;
converse . connection . reset ( ) ;
}
2016-02-28 10:00:22 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
this . saveWindowState = function ( ev , hidden ) {
// XXX: eventually we should be able to just use
// document.visibilityState (when we drop support for older
// browsers).
var state ;
var v = "visible" , h = "hidden" ,
event _map = {
'focus' : v ,
'focusin' : v ,
'pageshow' : v ,
'blur' : h ,
'focusout' : h ,
'pagehide' : h
} ;
ev = ev || document . createEvent ( 'Events' ) ;
if ( ev . type in event _map ) {
state = event _map [ ev . type ] ;
} else {
state = document [ hidden ] ? "hidden" : "visible" ;
}
if ( state === 'visible' ) {
converse . clearMsgCounter ( ) ;
}
converse . windowState = state ;
} ;
2016-02-28 10:00:22 +01:00
this . registerGlobalEventHandlers = function ( ) {
2016-06-20 21:11:43 +02:00
// Taken from:
// http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active
var hidden = "hidden" ;
// Standards:
if ( hidden in document ) {
document . addEventListener ( "visibilitychange" , _ . partial ( converse . saveWindowState , _ , hidden ) ) ;
} else if ( ( hidden = "mozHidden" ) in document ) {
document . addEventListener ( "mozvisibilitychange" , _ . partial ( converse . saveWindowState , _ , hidden ) ) ;
} else if ( ( hidden = "webkitHidden" ) in document ) {
document . addEventListener ( "webkitvisibilitychange" , _ . partial ( converse . saveWindowState , _ , hidden ) ) ;
} else if ( ( hidden = "msHidden" ) in document ) {
document . addEventListener ( "msvisibilitychange" , _ . partial ( converse . saveWindowState , _ , hidden ) ) ;
} else if ( "onfocusin" in document ) {
// IE 9 and lower:
document . onfocusin = document . onfocusout = _ . partial ( converse . saveWindowState , _ , hidden ) ;
} else {
// All others:
window . onpageshow = window . onpagehide = window . onfocus = window . onblur = _ . partial ( converse . saveWindowState , _ , hidden ) ;
}
// set the initial state (but only if browser supports the Page Visibility API)
if ( document [ hidden ] !== undefined ) {
_ . partial ( converse . saveWindowState , _ , hidden ) ( { type : document [ hidden ] ? "blur" : "focus" } ) ;
}
2016-03-16 12:49:35 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
this . afterReconnected = function ( ) {
2016-07-26 08:00:30 +02:00
this . registerPresenceHandler ( ) ;
2016-03-16 12:49:35 +01:00
this . chatboxes . registerMessageHandler ( ) ;
this . xmppstatus . sendPresence ( ) ;
this . giveFeedback ( _ _ ( 'Contacts' ) ) ;
2016-02-26 18:28:18 +01:00
} ;
2016-02-28 10:00:22 +01:00
this . onReconnected = function ( ) {
// We need to re-register all the event handlers on the newly
// created connection.
var deferred = new $ . Deferred ( ) ;
2016-09-16 14:35:02 +02:00
converse . initStatus ( ) . done ( function ( ) {
converse . afterReconnected ( ) ;
2016-02-28 10:00:22 +01:00
deferred . resolve ( ) ;
2016-09-16 14:35:02 +02:00
} ) ;
2016-03-29 08:56:13 +02:00
converse . emit ( 'reconnected' ) ;
2016-02-28 10:00:22 +01:00
return deferred . promise ( ) ;
2016-02-26 18:28:18 +01:00
} ;
2016-02-28 10:00:22 +01:00
this . enableCarbons = function ( ) {
/ * A s k t h e X M P P s e r v e r t o e n a b l e M e s s a g e C a r b o n s
* See XEP - 0280 https : //xmpp.org/extensions/xep-0280.html#enabling
* /
if ( ! this . message _carbons || this . session . get ( 'carbons_enabled' ) ) {
2016-02-26 18:28:18 +01:00
return ;
}
2016-02-28 10:00:22 +01:00
var carbons _iq = new Strophe . Builder ( 'iq' , {
from : this . connection . jid ,
id : 'enablecarbons' ,
type : 'set'
} )
. c ( 'enable' , { xmlns : Strophe . NS . CARBONS } ) ;
this . connection . addHandler ( function ( iq ) {
if ( $ ( iq ) . find ( 'error' ) . length > 0 ) {
converse . log ( 'ERROR: An error occured while trying to enable message carbons.' ) ;
} else {
this . session . save ( { carbons _enabled : true } ) ;
converse . log ( 'Message carbons have been enabled.' ) ;
}
} . bind ( this ) , null , "iq" , null , "enablecarbons" ) ;
this . connection . send ( carbons _iq ) ;
2016-02-26 18:28:18 +01:00
} ;
2016-07-26 08:00:30 +02:00
this . initRoster = function ( ) {
2016-09-21 17:24:32 +02:00
converse . roster = new converse . RosterContacts ( ) ;
converse . roster . browserStorage = new Backbone . BrowserStorage . session (
b64 _sha1 ( 'converse.contacts-' + converse . bare _jid ) ) ;
converse . rostergroups = new converse . RosterGroups ( ) ;
converse . rostergroups . browserStorage = new Backbone . BrowserStorage . session (
2016-07-26 08:00:30 +02:00
b64 _sha1 ( 'converse.roster.groups' + converse . bare _jid ) ) ;
} ;
2016-09-21 17:24:32 +02:00
this . populateRoster = function ( ) {
/ * F e t c h a l l t h e r o s t e r g r o u p s , a n d t h e n t h e r o s t e r c o n t a c t s .
* Emit an event after fetching is done in each case .
* /
converse . rostergroups . fetchRosterGroups ( ) . then ( function ( ) {
converse . emit ( 'rosterGroupsFetched' ) ;
converse . roster . fetchRosterContacts ( ) . then ( function ( ) {
converse . emit ( 'rosterContactsFetched' ) ;
converse . sendInitialPresence ( ) ;
} ) ;
} ) ;
} ;
2016-07-26 08:00:30 +02:00
this . unregisterPresenceHandler = function ( ) {
2016-09-21 17:24:32 +02:00
if ( typeof converse . presence _ref !== 'undefined' ) {
converse . connection . deleteHandler ( converse . presence _ref ) ;
delete converse . presence _ref ;
2016-07-26 08:00:30 +02:00
}
} ;
this . registerPresenceHandler = function ( ) {
2016-09-21 17:24:32 +02:00
converse . unregisterPresenceHandler ( ) ;
converse . presence _ref = converse . connection . addHandler (
2016-07-26 08:00:30 +02:00
function ( presence ) {
converse . roster . presenceHandler ( presence ) ;
return true ;
} , null , 'presence' , null ) ;
} ;
2016-09-21 17:24:32 +02:00
this . sendInitialPresence = function ( ) {
if ( converse . send _initial _presence ) {
converse . xmppstatus . sendPresence ( ) ;
}
} ;
2016-07-26 08:00:30 +02:00
this . onStatusInitialized = function ( ) {
this . registerIntervalHandler ( ) ;
this . initRoster ( ) ;
2016-09-21 17:24:32 +02:00
this . populateRoster ( ) ;
2016-03-16 12:49:35 +01:00
this . chatboxes . onConnected ( ) ;
2016-07-26 08:00:30 +02:00
this . registerPresenceHandler ( ) ;
2016-03-16 12:49:35 +01:00
this . giveFeedback ( _ _ ( 'Contacts' ) ) ;
if ( typeof this . callback === 'function' ) {
2016-06-20 21:11:43 +02:00
// XXX: Deprecate in favor of init_deferred
this . callback ( ) ;
}
if ( converse . connection . service === 'jasmine tests' ) {
init _deferred . resolve ( converse ) ;
} else {
init _deferred . resolve ( ) ;
2016-03-16 12:49:35 +01:00
}
2016-03-19 14:33:31 +01:00
converse . emit ( 'initialized' ) ;
2016-03-16 12:49:35 +01:00
} ;
2016-02-28 10:00:22 +01:00
this . onConnected = function ( callback ) {
// When reconnecting, there might be some open chat boxes. We don't
// know whether these boxes are of the same account or not, so we
// close them now.
2016-05-24 10:23:19 +02:00
// XXX: ran into an issue where a returned PubSub BOSH response was
// not received by the browser. The solution was to flush the
// connection early on. I don't know what the underlying cause of
// this issue is, and whether it's a Strophe.js or Prosody bug.
// My suspicion is that Prosody replies to an invalid/expired
// Request, which is why the browser then doesn't receive it.
// In any case, flushing here (sending out a new BOSH request)
// solves the problem.
converse . connection . flush ( ) ;
/ * C a l l e d a s s o o n a s a n e w c o n n e c t i o n h a s b e e n e s t a b l i s h e d , e i t h e r
* by logging in or by attaching to an existing BOSH session .
* /
2016-02-28 10:00:22 +01:00
this . chatboxviews . closeAllChatBoxes ( ) ;
this . jid = this . connection . jid ;
this . bare _jid = Strophe . getBareJidFromJid ( this . connection . jid ) ;
this . resource = Strophe . getResourceFromJid ( this . connection . jid ) ;
this . domain = Strophe . getDomainFromJid ( this . connection . jid ) ;
this . features = new this . Features ( ) ;
this . enableCarbons ( ) ;
2016-06-20 21:11:43 +02:00
this . initStatus ( ) . done ( _ . bind ( this . onStatusInitialized , this ) ) ;
2016-03-16 12:49:35 +01:00
converse . emit ( 'connected' ) ;
converse . emit ( 'ready' ) ; // BBB: Will be removed.
2016-02-26 18:28:18 +01:00
} ;
2016-03-07 18:54:07 +01:00
this . RosterContact = Backbone . Model . extend ( {
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
initialize : function ( attributes , options ) {
var jid = attributes . jid ;
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
var resource = Strophe . getResourceFromJid ( jid ) ;
attributes . jid = bare _jid ;
this . set ( _ . extend ( {
'id' : bare _jid ,
'jid' : bare _jid ,
'fullname' : bare _jid ,
'chat_status' : 'offline' ,
'user_id' : Strophe . getNodeFromJid ( jid ) ,
'resources' : resource ? [ resource ] : [ ] ,
'groups' : [ ] ,
'image_type' : 'image/png' ,
'image' : "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg==" ,
'status' : ''
} , attributes ) ) ;
this . on ( 'destroy' , function ( ) { this . removeFromRoster ( ) ; } . bind ( this ) ) ;
2016-03-08 13:16:04 +01:00
this . on ( 'change:chat_status' , function ( item ) {
converse . emit ( 'contactStatusChanged' , item . attributes ) ;
} ) ;
2016-03-07 18:54:07 +01:00
} ,
subscribe : function ( message ) {
/ * S e n d a p r e s e n c e s u b s c r i p t i o n r e q u e s t t o t h i s r o s t e r c o n t a c t
2016-03-19 14:33:31 +01:00
*
* Parameters :
* ( String ) message - An optional message to explain the
* reason for the subscription request .
* /
2016-03-07 18:54:07 +01:00
this . save ( 'ask' , "subscribe" ) ; // ask === 'subscribe' Means we have ask to subscribe to them.
var pres = $pres ( { to : this . get ( 'jid' ) , type : "subscribe" } ) ;
if ( message && message !== "" ) {
pres . c ( "status" ) . t ( message ) . up ( ) ;
}
var nick = converse . xmppstatus . get ( 'fullname' ) ;
if ( nick && nick !== "" ) {
pres . c ( 'nick' , { 'xmlns' : Strophe . NS . NICK } ) . t ( nick ) . up ( ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
converse . connection . send ( pres ) ;
return this ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
ackSubscribe : function ( ) {
/ * U p o n r e c e i v i n g t h e p r e s e n c e s t a n z a o f t y p e " s u b s c r i b e d " ,
2016-03-19 14:33:31 +01:00
* the user SHOULD acknowledge receipt of that subscription
* state notification by sending a presence stanza of type
* "subscribe" to the contact
* /
2016-03-07 18:54:07 +01:00
converse . connection . send ( $pres ( {
'type' : 'subscribe' ,
'to' : this . get ( 'jid' )
} ) ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
ackUnsubscribe : function ( jid ) {
/ * U p o n r e c e i v i n g t h e p r e s e n c e s t a n z a o f t y p e " u n s u b s c r i b e d " ,
2016-03-19 14:33:31 +01:00
* the user SHOULD acknowledge receipt of that subscription state
* notification by sending a presence stanza of type "unsubscribe"
* this step lets the user ' s server know that it MUST no longer
* send notification of the subscription state change to the user .
* Parameters :
* ( String ) jid - The Jabber ID of the user who is unsubscribing
* /
2016-03-07 18:54:07 +01:00
converse . connection . send ( $pres ( { 'type' : 'unsubscribe' , 'to' : this . get ( 'jid' ) } ) ) ;
this . destroy ( ) ; // Will cause removeFromRoster to be called.
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
unauthorize : function ( message ) {
/ * U n a u t h o r i z e t h i s c o n t a c t ' s p r e s e n c e s u b s c r i p t i o n
2016-03-19 14:33:31 +01:00
* Parameters :
* ( String ) message - Optional message to send to the person being unauthorized
* /
2016-03-07 18:54:07 +01:00
converse . rejectPresenceSubscription ( this . get ( 'jid' ) , message ) ;
return this ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
authorize : function ( message ) {
/ * A u t h o r i z e p r e s e n c e s u b s c r i p t i o n
2016-03-19 14:33:31 +01:00
* Parameters :
* ( String ) message - Optional message to send to the person being authorized
* /
2016-03-07 18:54:07 +01:00
var pres = $pres ( { to : this . get ( 'jid' ) , type : "subscribed" } ) ;
if ( message && message !== "" ) {
pres . c ( "status" ) . t ( message ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
converse . connection . send ( pres ) ;
return this ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
removeResource : function ( resource ) {
var resources = this . get ( 'resources' ) , idx ;
if ( resource ) {
idx = _ . indexOf ( resources , resource ) ;
if ( idx !== - 1 ) {
resources . splice ( idx , 1 ) ;
this . save ( { 'resources' : resources } ) ;
}
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
else {
// if there is no resource (resource is null), it probably
// means that the user is now completely offline. To make sure
// that there isn't any "ghost" resources left, we empty the array
this . save ( { 'resources' : [ ] } ) ;
return 0 ;
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
return resources . length ;
} ,
removeFromRoster : function ( callback ) {
/ * I n s t r u c t t h e X M P P s e r v e r t o r e m o v e t h i s c o n t a c t f r o m o u r r o s t e r
2016-03-19 14:33:31 +01:00
* Parameters :
* ( Function ) callback
* /
2016-03-07 18:54:07 +01:00
var iq = $iq ( { type : 'set' } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } )
. c ( 'item' , { jid : this . get ( 'jid' ) , subscription : "remove" } ) ;
converse . connection . sendIQ ( iq , callback , callback ) ;
return this ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
this . RosterContacts = Backbone . Collection . extend ( {
model : converse . RosterContact ,
comparator : function ( contact1 , contact2 ) {
var name1 , name2 ;
var status1 = contact1 . get ( 'chat_status' ) || 'offline' ;
var status2 = contact2 . get ( 'chat_status' ) || 'offline' ;
if ( converse . STATUS _WEIGHTS [ status1 ] === converse . STATUS _WEIGHTS [ status2 ] ) {
name1 = contact1 . get ( 'fullname' ) . toLowerCase ( ) ;
name2 = contact2 . get ( 'fullname' ) . toLowerCase ( ) ;
return name1 < name2 ? - 1 : ( name1 > name2 ? 1 : 0 ) ;
} else {
return converse . STATUS _WEIGHTS [ status1 ] < converse . STATUS _WEIGHTS [ status2 ] ? - 1 : 1 ;
}
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-09-21 17:24:32 +02:00
fetchRosterContacts : function ( ) {
/ * F e t c h e s t h e r o s t e r c o n t a c t s , f i r s t b y t r y i n g t h e
* sessionStorage cache , and if that ' s empty , then by querying
* the XMPP server .
*
* Returns a promise which resolves once the contacts have been
* fetched .
* /
var deferred = new $ . Deferred ( ) ;
this . fetch ( {
add : true ,
success : function ( collection ) {
if ( collection . length === 0 ) {
/ * W e d o n ' t h a v e a n y r o s t e r c o n t a c t s s t o r e d i n s e s s i o n S t o r a g e ,
* so lets fetch the roster from the XMPP server . We pass in
* 'sendPresence' as callback method , because after initially
* fetching the roster we are ready to receive presence
* updates from our contacts .
* /
converse . send _initial _presence = true ;
converse . roster . fetchFromServer ( deferred . resolve ) ;
} else {
converse . emit ( 'cachedRoster' , collection ) ;
deferred . resolve ( ) ;
}
}
} ) ;
return deferred . promise ( ) ;
} ,
2016-03-07 18:54:07 +01:00
subscribeToSuggestedItems : function ( msg ) {
$ ( msg ) . find ( 'item' ) . each ( function ( i , items ) {
if ( this . getAttribute ( 'action' ) === 'add' ) {
converse . roster . addAndSubscribe (
this . getAttribute ( 'jid' ) , null , converse . xmppstatus . get ( 'fullname' ) ) ;
}
} ) ;
return true ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
isSelf : function ( jid ) {
return ( Strophe . getBareJidFromJid ( jid ) === Strophe . getBareJidFromJid ( converse . connection . jid ) ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
addAndSubscribe : function ( jid , name , groups , message , attributes ) {
/ * A d d a r o s t e r c o n t a c t a n d t h e n o n c e w e h a v e c o n f i r m a t i o n f r o m
2016-03-19 14:33:31 +01:00
* the XMPP server we subscribe to that contact ' s presence updates .
* Parameters :
* ( String ) jid - The Jabber ID of the user being added and subscribed to .
* ( String ) name - The name of that user
* ( Array of Strings ) groups - Any roster groups the user might belong to
* ( String ) message - An optional message to explain the
* reason for the subscription request .
* ( Object ) attributes - Any additional attributes to be stored on the user ' s model .
* /
2016-03-07 18:54:07 +01:00
this . addContact ( jid , name , groups , attributes ) . done ( function ( contact ) {
if ( contact instanceof converse . RosterContact ) {
contact . subscribe ( message ) ;
}
} ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
sendContactAddIQ : function ( jid , name , groups , callback , errback ) {
/ * S e n d a n I Q s t a n z a t o t h e X M P P s e r v e r t o a d d a n e w r o s t e r c o n t a c t .
*
* Parameters :
* ( String ) jid - The Jabber ID of the user being added
* ( String ) name - The name of that user
* ( Array of Strings ) groups - Any roster groups the user might belong to
2016-03-16 12:49:35 +01:00
* ( Function ) callback - A function to call once the IQ is returned
2016-03-07 18:54:07 +01:00
* ( Function ) errback - A function to call if an error occured
2016-02-28 10:00:22 +01:00
* /
2016-03-07 18:54:07 +01:00
name = _ . isEmpty ( name ) ? jid : name ;
var iq = $iq ( { type : 'set' } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } )
. c ( 'item' , { jid : jid , name : name } ) ;
_ . map ( groups , function ( group ) { iq . c ( 'group' ) . t ( group ) . up ( ) ; } ) ;
converse . connection . sendIQ ( iq , callback , errback ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
addContact : function ( jid , name , groups , attributes ) {
/ * A d d s a R o s t e r C o n t a c t i n s t a n c e t o c o n v e r s e . r o s t e r a n d
* registers the contact on the XMPP server .
* Returns a promise which is resolved once the XMPP server has
* responded .
2016-02-28 10:00:22 +01:00
*
2016-03-07 18:54:07 +01:00
* Parameters :
* ( String ) jid - The Jabber ID of the user being added and subscribed to .
* ( String ) name - The name of that user
* ( Array of Strings ) groups - Any roster groups the user might belong to
* ( Object ) attributes - Any additional attributes to be stored on the user ' s model .
2016-02-28 10:00:22 +01:00
* /
2016-03-07 18:54:07 +01:00
var deferred = new $ . Deferred ( ) ;
groups = groups || [ ] ;
name = _ . isEmpty ( name ) ? jid : name ;
this . sendContactAddIQ ( jid , name , groups ,
function ( iq ) {
var contact = this . create ( _ . extend ( {
ask : undefined ,
fullname : name ,
groups : groups ,
jid : jid ,
requesting : false ,
subscription : 'none'
} , attributes ) , { sort : false } ) ;
deferred . resolve ( contact ) ;
2016-02-28 10:00:22 +01:00
} . bind ( this ) ,
2016-03-07 18:54:07 +01:00
function ( err ) {
alert ( _ _ ( "Sorry, there was an error while trying to add " + name + " as a contact." ) ) ;
converse . log ( err ) ;
deferred . resolve ( err ) ;
}
2016-02-28 10:00:22 +01:00
) ;
2016-03-07 18:54:07 +01:00
return deferred . promise ( ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
addResource : function ( bare _jid , resource ) {
var item = this . get ( bare _jid ) ,
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 ] } ) ;
}
}
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
subscribeBack : function ( bare _jid ) {
var contact = this . get ( bare _jid ) ;
if ( contact instanceof converse . RosterContact ) {
contact . authorize ( ) . subscribe ( ) ;
} else {
// Can happen when a subscription is retried or roster was deleted
this . addContact ( bare _jid , '' , [ ] , { 'subscription' : 'from' } ) . done ( function ( contact ) {
if ( contact instanceof converse . RosterContact ) {
contact . authorize ( ) . subscribe ( ) ;
}
} ) ;
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
} ,
2016-03-07 18:54:07 +01:00
getNumOnlineContacts : function ( ) {
var count = 0 ,
ignored = [ 'offline' , 'unavailable' ] ,
models = this . models ,
models _length = models . length ,
i ;
if ( converse . show _only _online _users ) {
ignored = _ . union ( ignored , [ 'dnd' , 'xa' , 'away' ] ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
for ( i = 0 ; i < models _length ; i ++ ) {
if ( _ . indexOf ( ignored , models [ i ] . get ( 'chat_status' ) ) === - 1 ) {
count ++ ;
}
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
return count ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
onRosterPush : function ( iq ) {
/ * H a n d l e r o s t e r u p d a t e s f r o m t h e X M P P s e r v e r .
2016-03-19 14:33:31 +01:00
* See : https : //xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
*
* Parameters :
* ( XMLElement ) IQ - The IQ stanza received from the XMPP server .
* /
2016-03-07 18:54:07 +01:00
var id = iq . getAttribute ( 'id' ) ;
var from = iq . getAttribute ( 'from' ) ;
if ( from && from !== "" && Strophe . getBareJidFromJid ( from ) !== converse . bare _jid ) {
// Receiving client MUST ignore stanza unless it has no from or from = user's bare JID.
// XXX: Some naughty servers apparently send from a full
// JID so we need to explicitly compare bare jids here.
// https://github.com/jcbrand/converse.js/issues/493
converse . connection . send (
$iq ( { type : 'error' , id : id , from : converse . connection . jid } )
. c ( 'error' , { 'type' : 'cancel' } )
. c ( 'service-unavailable' , { 'xmlns' : Strophe . NS . ROSTER } )
) ;
return true ;
}
converse . connection . send ( $iq ( { type : 'result' , id : id , from : converse . connection . jid } ) ) ;
$ ( iq ) . children ( 'query' ) . find ( 'item' ) . each ( function ( idx , item ) {
this . updateContact ( item ) ;
} . bind ( this ) ) ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
converse . emit ( 'rosterPush' , iq ) ;
return true ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
fetchFromServer : function ( callback ) {
/* Get the roster from the XMPP server */
var iq = $iq ( { type : 'get' , 'id' : converse . connection . getUniqueId ( 'roster' ) } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } ) ;
return converse . connection . sendIQ ( iq , function ( ) {
this . onReceivedFromServer . apply ( this , arguments ) ;
callback . apply ( this , arguments ) ;
} . bind ( this ) ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
onReceivedFromServer : function ( iq ) {
/ * A n I Q s t a n z a c o n t a i n i n g t h e r o s t e r h a s b e e n r e c e i v e d f r o m
2016-03-19 14:33:31 +01:00
* the XMPP server .
* /
2016-03-07 18:54:07 +01:00
$ ( iq ) . children ( 'query' ) . find ( 'item' ) . each ( function ( idx , item ) {
this . updateContact ( item ) ;
} . bind ( this ) ) ;
2016-09-16 14:35:02 +02:00
converse . emit ( 'roster' , iq ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
updateContact : function ( item ) {
/ * U p d a t e o r c r e a t e R o s t e r C o n t a c t m o d e l s b a s e d o n i t e m s
2016-03-19 14:33:31 +01:00
* received in the IQ from the server .
* /
2016-03-07 18:54:07 +01:00
var jid = item . getAttribute ( 'jid' ) ;
if ( this . isSelf ( jid ) ) { return ; }
var groups = [ ] ,
contact = this . get ( jid ) ,
ask = item . getAttribute ( "ask" ) ,
subscription = item . getAttribute ( "subscription" ) ;
$ . map ( item . getElementsByTagName ( 'group' ) , function ( group ) {
groups . push ( Strophe . getText ( group ) ) ;
} ) ;
if ( ! contact ) {
if ( ( subscription === "none" && ask === null ) || ( subscription === "remove" ) ) {
return ; // We're lazy when adding contacts.
}
this . create ( {
ask : ask ,
fullname : item . getAttribute ( "name" ) || jid ,
groups : groups ,
jid : jid ,
subscription : subscription
} , { sort : false } ) ;
} else {
if ( subscription === "remove" ) {
return contact . destroy ( ) ; // will trigger removeFromRoster
}
// We only find out about requesting contacts via the
// presence handler, so if we receive a contact
// here, we know they aren't requesting anymore.
// see docs/DEVELOPER.rst
contact . save ( {
subscription : subscription ,
ask : ask ,
requesting : null ,
groups : groups
} ) ;
2016-02-28 10:00:22 +01:00
}
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
createRequestingContact : function ( presence ) {
/ * C r e a t e s a R e q u e s t i n g C o n t a c t .
*
* Note : this method gets completely overridden by converse - vcard . js
2016-02-28 10:00:22 +01:00
* /
2016-03-16 12:49:35 +01:00
var bare _jid = Strophe . getBareJidFromJid ( presence . getAttribute ( 'from' ) ) ;
var nick = $ ( presence ) . children ( 'nick[xmlns=' + Strophe . NS . NICK + ']' ) . text ( ) ;
2016-03-07 18:54:07 +01:00
var user _data = {
jid : bare _jid ,
subscription : 'none' ,
ask : null ,
requesting : true ,
2016-03-16 12:49:35 +01:00
fullname : nick || bare _jid ,
2016-03-07 18:54:07 +01:00
} ;
this . create ( user _data ) ;
converse . emit ( 'contactRequest' , user _data ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
handleIncomingSubscription : function ( presence ) {
var jid = presence . getAttribute ( 'from' ) ;
2016-03-07 18:54:07 +01:00
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
var contact = this . get ( bare _jid ) ;
if ( ! converse . allow _contact _requests ) {
2016-03-16 12:49:35 +01:00
converse . rejectPresenceSubscription (
jid ,
_ _ ( "This client does not allow presence subscriptions" )
) ;
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
if ( converse . auto _subscribe ) {
if ( ( ! contact ) || ( contact . get ( 'subscription' ) !== 'to' ) ) {
this . subscribeBack ( bare _jid ) ;
2016-02-28 10:00:22 +01:00
} else {
2016-03-07 18:54:07 +01:00
contact . authorize ( ) ;
2016-02-28 10:00:22 +01:00
}
2016-02-26 18:28:18 +01:00
} else {
2016-03-07 18:54:07 +01:00
if ( contact ) {
if ( contact . get ( 'subscription' ) !== 'none' ) {
contact . authorize ( ) ;
} else if ( contact . get ( 'ask' ) === "subscribe" ) {
contact . authorize ( ) ;
}
} else if ( ! contact ) {
2016-03-16 12:49:35 +01:00
this . createRequestingContact ( presence ) ;
2016-03-07 18:54:07 +01:00
}
2016-02-26 18:28:18 +01:00
}
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
presenceHandler : function ( presence ) {
var $presence = $ ( presence ) ,
presence _type = presence . getAttribute ( 'type' ) ;
if ( presence _type === 'error' ) { return true ; }
var jid = presence . getAttribute ( 'from' ) ,
bare _jid = Strophe . getBareJidFromJid ( jid ) ,
resource = Strophe . getResourceFromJid ( jid ) ,
chat _status = $presence . find ( 'show' ) . text ( ) || 'online' ,
status _message = $presence . find ( 'status' ) ,
contact = this . get ( bare _jid ) ;
if ( this . isSelf ( bare _jid ) ) {
2016-03-19 14:33:31 +01:00
if ( ( converse . connection . jid !== jid ) &&
( presence _type !== 'unavailable' ) &&
( converse . synchronize _availability === true ||
converse . synchronize _availability === resource ) ) {
// Another resource has changed its status and
// synchronize_availability option set to update,
// we'll update ours as well.
2016-03-07 18:54:07 +01:00
converse . xmppstatus . save ( { 'status' : chat _status } ) ;
2016-03-19 14:33:31 +01:00
if ( status _message . length ) {
converse . xmppstatus . save ( {
'status_message' : status _message . text ( )
} ) ;
}
2016-03-07 18:54:07 +01:00
}
return ;
} else if ( ( $presence . find ( 'x' ) . attr ( 'xmlns' ) || '' ) . indexOf ( Strophe . NS . MUC ) === 0 ) {
return ; // Ignore MUC
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
if ( contact && ( status _message . text ( ) !== contact . get ( 'status' ) ) ) {
contact . save ( { 'status' : status _message . text ( ) } ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
if ( presence _type === 'subscribed' && contact ) {
contact . ackSubscribe ( ) ;
} else if ( presence _type === 'unsubscribed' && contact ) {
contact . ackUnsubscribe ( ) ;
} else if ( presence _type === 'unsubscribe' ) {
return ;
} else if ( presence _type === 'subscribe' ) {
2016-03-16 12:49:35 +01:00
this . handleIncomingSubscription ( presence ) ;
2016-03-07 18:54:07 +01:00
} else if ( presence _type === 'unavailable' && contact ) {
// Only set the user to offline if there aren't any
// other resources still available.
if ( contact . removeResource ( resource ) === 0 ) {
contact . save ( { 'chat_status' : "offline" } ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
} else if ( contact ) { // presence_type is undefined
this . addResource ( bare _jid , resource ) ;
contact . save ( { 'chat_status' : chat _status } ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
}
} ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
2016-07-26 08:00:30 +02:00
this . RosterGroup = Backbone . Model . extend ( {
initialize : function ( attributes , options ) {
this . set ( _ . extend ( {
description : DESC _GROUP _TOGGLE ,
state : converse . OPENED
} , attributes ) ) ;
// Collection of contacts belonging to this group.
this . contacts = new converse . RosterContacts ( ) ;
}
} ) ;
this . RosterGroups = Backbone . Collection . extend ( {
model : converse . RosterGroup ,
2016-09-21 17:24:32 +02:00
fetchRosterGroups : function ( ) {
/ * F e t c h e s a l l t h e r o s t e r g r o u p s f r o m s e s s i o n S t o r a g e .
*
* Returns a promise which resolves once the groups have been
* returned .
* /
var deferred = new $ . Deferred ( ) ;
this . fetch ( {
silent : true , // We need to first have all groups before
// we can start positioning them, so we set
// 'silent' to true.
success : deferred . resolve
} ) ;
return deferred . promise ( ) ;
}
2016-07-26 08:00:30 +02:00
} ) ;
2016-03-07 18:54:07 +01:00
this . Message = Backbone . Model . extend ( {
defaults : function ( ) {
return {
msgid : converse . connection . getUniqueId ( )
} ;
}
} ) ;
2016-02-28 10:00:22 +01:00
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
this . Messages = Backbone . Collection . extend ( {
model : converse . Message ,
comparator : 'time'
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
this . ChatBox = Backbone . Model . extend ( {
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
initialize : function ( ) {
this . messages = new converse . Messages ( ) ;
2016-09-16 14:35:02 +02:00
this . messages . browserStorage = new Backbone . BrowserStorage [ converse . message _storage ] (
2016-03-07 18:54:07 +01:00
b64 _sha1 ( 'converse.messages' + this . get ( 'jid' ) + converse . bare _jid ) ) ;
2016-04-01 15:33:14 +02:00
this . save ( {
2016-03-07 18:54:07 +01:00
// The chat_state will be set to ACTIVE once the chat box is opened
// and we listen for change:chat_state, so shouldn't set it to ACTIVE here.
'box_id' : b64 _sha1 ( this . get ( 'jid' ) ) ,
2016-04-01 15:33:14 +02:00
'chat_state' : undefined ,
'num_unread' : this . get ( 'num_unread' ) || 0 ,
2016-03-07 18:54:07 +01:00
'time_opened' : this . get ( 'time_opened' ) || moment ( ) . valueOf ( ) ,
'url' : '' ,
'user_id' : Strophe . getNodeFromJid ( this . get ( 'jid' ) )
2016-04-01 15:33:14 +02:00
} ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-06-20 21:11:43 +02:00
getMessageAttributes : function ( $message , $delay , original _stanza ) {
2016-03-07 18:54:07 +01:00
$delay = $delay || $message . find ( 'delay' ) ;
2016-07-26 08:00:30 +02:00
var type = $message . attr ( 'type' ) ,
body , stamp , time , sender , from ;
if ( type === 'error' ) {
body = $message . find ( 'error' ) . children ( 'text' ) . text ( ) ;
} else {
body = $message . children ( 'body' ) . text ( ) ;
}
var delayed = $delay . length > 0 ,
2016-03-07 18:54:07 +01:00
fullname = this . get ( 'fullname' ) ,
2016-07-26 08:00:30 +02:00
is _groupchat = type === 'groupchat' ,
2016-03-07 18:54:07 +01:00
chat _state = $message . find ( converse . COMPOSING ) . length && converse . COMPOSING ||
$message . find ( converse . PAUSED ) . length && converse . PAUSED ||
$message . find ( converse . INACTIVE ) . length && converse . INACTIVE ||
$message . find ( converse . ACTIVE ) . length && converse . ACTIVE ||
2016-07-26 08:00:30 +02:00
$message . find ( converse . GONE ) . length && converse . GONE ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
if ( is _groupchat ) {
from = Strophe . unescapeNode ( Strophe . getResourceFromJid ( $message . attr ( 'from' ) ) ) ;
} else {
from = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-29 08:56:13 +02:00
if ( _ . isEmpty ( fullname ) ) {
fullname = from ;
}
2016-03-07 18:54:07 +01:00
if ( delayed ) {
stamp = $delay . attr ( 'stamp' ) ;
time = stamp ;
} else {
time = moment ( ) . format ( ) ;
}
if ( ( is _groupchat && from === this . get ( 'nick' ) ) || ( ! is _groupchat && from === converse . bare _jid ) ) {
sender = 'me' ;
} else {
sender = 'them' ;
2016-02-28 10:00:22 +01:00
}
2016-06-20 21:11:43 +02:00
return {
2016-07-26 08:00:30 +02:00
'type' : type ,
'chat_state' : chat _state ,
'delayed' : delayed ,
'fullname' : fullname ,
'message' : body || undefined ,
'msgid' : $message . attr ( 'id' ) ,
'sender' : sender ,
'time' : time
2016-06-20 21:11:43 +02:00
} ;
} ,
createMessage : function ( $message , $delay , original _stanza ) {
return this . messages . create ( this . getMessageAttributes . apply ( this , arguments ) ) ;
2016-03-07 18:54:07 +01:00
}
} ) ;
2016-06-20 21:11:43 +02:00
2016-03-16 12:49:35 +01:00
this . ChatBoxes = Backbone . Collection . extend ( {
model : converse . ChatBox ,
comparator : 'time_opened' ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
registerMessageHandler : function ( ) {
2016-07-26 08:00:30 +02:00
converse . connection . addHandler ( this . onMessage . bind ( this ) , null , 'message' , 'chat' ) ;
converse . connection . addHandler ( this . onErrorMessage . bind ( this ) , null , 'message' , 'error' ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-05-03 17:37:10 +02:00
chatBoxMayBeShown : function ( chatbox ) {
2016-03-16 12:49:35 +01:00
return true ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
onChatBoxesFetched : function ( collection ) {
/ * S h o w c h a t b o x e s u p o n r e c e i v i n g t h e m f r o m s e s s i o n S t o r a g e
2016-03-07 18:54:07 +01:00
*
2016-03-16 12:49:35 +01:00
* This method gets overridden entirely in src / converse - controlbox . js
* if the controlbox plugin is active .
2016-03-07 18:54:07 +01:00
* /
2016-03-16 12:49:35 +01:00
collection . each ( function ( chatbox ) {
2016-05-03 17:37:10 +02:00
if ( this . chatBoxMayBeShown ( chatbox ) ) {
2016-03-16 12:49:35 +01:00
chatbox . trigger ( 'show' ) ;
}
} . bind ( this ) ) ;
2016-05-03 17:37:10 +02:00
converse . emit ( 'chatBoxesFetched' ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
onConnected : function ( ) {
this . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
b64 _sha1 ( 'converse.chatboxes-' + converse . bare _jid ) ) ;
this . registerMessageHandler ( ) ;
this . fetch ( {
add : true ,
success : this . onChatBoxesFetched . bind ( this )
} ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-07-26 08:00:30 +02:00
onErrorMessage : function ( message ) {
/ * H a n d l e r m e t h o d f o r a l l i n c o m i n g e r r o r m e s s a g e s t a n z a s
* /
// TODO: we can likely just reuse "onMessage" below
var $message = $ ( message ) ,
from _jid = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ;
if ( from _jid === converse . bare _jid ) {
return true ;
}
// Get chat box, but only create a new one when the message has a body.
var chatbox = this . getChatBox ( from _jid ) ;
if ( ! chatbox ) {
return true ;
}
chatbox . createMessage ( $message , null , message ) ;
return true ;
} ,
2016-03-16 12:49:35 +01:00
onMessage : function ( message ) {
2016-03-29 08:56:13 +02:00
/ * H a n d l e r m e t h o d f o r a l l i n c o m i n g s i n g l e - u s e r c h a t " m e s s a g e "
* stanzas .
2016-03-07 18:54:07 +01:00
* /
2016-03-16 12:49:35 +01:00
var $message = $ ( message ) ,
2016-03-29 08:56:13 +02:00
contact _jid , $forwarded , $delay , from _bare _jid ,
from _resource , is _me , msgid ,
2016-03-16 12:49:35 +01:00
chatbox , resource ,
from _jid = $message . attr ( 'from' ) ,
to _jid = $message . attr ( 'to' ) ,
to _resource = Strophe . getResourceFromJid ( to _jid ) ;
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
if ( converse . filter _by _resource && ( to _resource && to _resource !== converse . resource ) ) {
2016-03-29 08:56:13 +02:00
converse . log (
'onMessage: Ignoring incoming message intended for a different resource: ' + to _jid ,
'info'
) ;
2016-03-16 12:49:35 +01:00
return true ;
2016-03-29 08:56:13 +02:00
} else if ( utils . isHeadlineMessage ( message ) ) {
// XXX: Ideally we wouldn't have to check for headline
// messages, but Prosody sends headline messages with the
// wrong type ('chat'), so we need to filter them out here.
converse . log (
"onMessage: Ignoring incoming headline message sent with type 'chat' from JID: " + from _jid ,
'info'
) ;
2016-03-16 12:49:35 +01:00
return true ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
$forwarded = $message . find ( 'forwarded' ) ;
if ( $forwarded . length ) {
$message = $forwarded . children ( 'message' ) ;
$delay = $forwarded . children ( 'delay' ) ;
from _jid = $message . attr ( 'from' ) ;
to _jid = $message . attr ( 'to' ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-16 12:49:35 +01:00
from _bare _jid = Strophe . getBareJidFromJid ( from _jid ) ;
from _resource = Strophe . getResourceFromJid ( from _jid ) ;
is _me = from _bare _jid === converse . bare _jid ;
msgid = $message . attr ( 'id' ) ;
if ( is _me ) {
// I am the sender, so this must be a forwarded message...
contact _jid = Strophe . getBareJidFromJid ( to _jid ) ;
resource = Strophe . getResourceFromJid ( to _jid ) ;
} else {
contact _jid = from _bare _jid ;
resource = from _resource ;
2016-02-26 18:28:18 +01:00
}
2016-08-12 22:52:21 +02:00
converse . emit ( 'message' , message ) ;
2016-03-16 12:49:35 +01:00
// Get chat box, but only create a new one when the message has a body.
chatbox = this . getChatBox ( contact _jid , $message . find ( 'body' ) . length > 0 ) ;
if ( ! chatbox ) {
return true ;
}
if ( msgid && chatbox . messages . findWhere ( { msgid : msgid } ) ) {
return true ; // We already have this message stored.
2016-02-26 18:28:18 +01:00
}
2016-06-20 21:11:43 +02:00
chatbox . createMessage ( $message , $delay , message ) ;
2016-03-16 12:49:35 +01:00
return true ;
2016-02-26 18:28:18 +01:00
} ,
2016-09-16 14:35:02 +02:00
getChatBox : function ( jid , create , attrs ) {
2016-03-16 12:49:35 +01:00
/ * R e t u r n s a c h a t b o x o r o p t i o n a l l y r e t u r n a n e w l y
* created one if one doesn ' t exist .
2016-03-07 18:54:07 +01:00
*
* Parameters :
2016-03-16 12:49:35 +01:00
* ( String ) jid - The JID of the user whose chat box we want
* ( Boolean ) create - Should a new chat box be created if none exists ?
2016-03-07 18:54:07 +01:00
* /
2016-03-16 12:49:35 +01:00
jid = jid . toLowerCase ( ) ;
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
var chatbox = this . get ( bare _jid ) ;
if ( ! chatbox && create ) {
var roster _item = converse . roster . get ( bare _jid ) ;
if ( roster _item === undefined ) {
converse . log ( 'Could not get roster item for JID ' + bare _jid , 'error' ) ;
return ;
}
2016-09-16 14:35:02 +02:00
chatbox = this . create ( _ . extend ( {
2016-03-16 12:49:35 +01:00
'id' : bare _jid ,
'jid' : bare _jid ,
'fullname' : _ . isEmpty ( roster _item . get ( 'fullname' ) ) ? jid : roster _item . get ( 'fullname' ) ,
'image_type' : roster _item . get ( 'image_type' ) ,
'image' : roster _item . get ( 'image' ) ,
'url' : roster _item . get ( 'url' )
2016-09-16 14:35:02 +02:00
} , attrs || { } ) ) ;
2016-03-16 12:49:35 +01:00
}
return chatbox ;
}
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
this . ChatBoxViews = Backbone . Overview . extend ( {
initialize : function ( ) {
this . model . on ( "add" , this . onChatBoxAdded , this ) ;
2016-03-29 08:56:13 +02:00
this . model . on ( "destroy" , this . removeChat , this ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
_ensureElement : function ( ) {
/ * O v e r r i d e m e t h o d f r o m b a c k b o n e . j s
* If the # conversejs element doesn ' t exist , create it .
2016-03-07 18:54:07 +01:00
* /
2016-03-16 12:49:35 +01:00
if ( ! this . el ) {
var $el = $ ( '#conversejs' ) ;
if ( ! $el . length ) {
$el = $ ( '<div id="conversejs">' ) ;
$ ( 'body' ) . append ( $el ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
$el . html ( converse . templates . chats _panel ( ) ) ;
this . setElement ( $el , false ) ;
} else {
this . setElement ( _ . result ( this , 'el' ) , false ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-16 12:49:35 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
onChatBoxAdded : function ( item ) {
2016-03-30 14:03:15 +02:00
// Views aren't created here, since the core code doesn't
2016-03-29 08:56:13 +02:00
// contain any views. Instead, they're created in overrides in
2016-03-30 14:03:15 +02:00
// plugins, such as in converse-chatview.js and converse-muc.js
return this . get ( item . get ( 'id' ) ) ;
2016-03-16 12:49:35 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-29 08:56:13 +02:00
removeChat : function ( item ) {
this . remove ( item . get ( 'id' ) ) ;
} ,
2016-03-16 12:49:35 +01:00
closeAllChatBoxes : function ( ) {
/ * T h i s m e t h o d g e t s o v e r r i d d e n i n s r c / c o n v e r s e - c o n t r o l b o x . j s i f
* the controlbox plugin is active .
2016-03-07 18:54:07 +01:00
* /
2016-03-16 12:49:35 +01:00
this . each ( function ( view ) { view . close ( ) ; } ) ;
return this ;
2016-02-26 18:28:18 +01:00
} ,
2016-05-03 17:37:10 +02:00
chatBoxMayBeShown : function ( chatbox ) {
return this . model . chatBoxMayBeShown ( chatbox ) ;
} ,
2016-06-20 21:11:43 +02:00
getChatBox : function ( attrs , create ) {
2016-03-16 12:49:35 +01:00
var chatbox = this . model . get ( attrs . jid ) ;
2016-06-20 21:11:43 +02:00
if ( ! chatbox && create ) {
2016-03-16 12:49:35 +01:00
chatbox = this . model . create ( attrs , {
'error' : function ( model , response ) {
converse . log ( response . responseText ) ;
}
} ) ;
2016-02-26 18:28:18 +01:00
}
2016-06-20 21:11:43 +02:00
return chatbox ;
} ,
showChat : function ( attrs ) {
/ * F i n d t h e c h a t b o x a n d s h o w i t ( i f i t m a y b e s h o w n ) .
* If it doesn ' t exist , create it .
* /
var chatbox = this . getChatBox ( attrs , true ) ;
2016-05-03 17:37:10 +02:00
if ( this . chatBoxMayBeShown ( chatbox ) ) {
chatbox . trigger ( 'show' , true ) ;
}
2016-03-16 12:49:35 +01:00
return chatbox ;
}
} ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
this . XMPPStatus = Backbone . Model . extend ( {
initialize : function ( ) {
this . set ( {
'status' : this . getStatus ( )
2016-03-07 18:54:07 +01:00
} ) ;
2016-03-16 12:49:35 +01:00
this . on ( 'change' , function ( item ) {
if ( _ . has ( item . changed , 'status' ) ) {
converse . emit ( 'statusChanged' , this . get ( 'status' ) ) ;
}
if ( _ . has ( item . changed , 'status_message' ) ) {
converse . emit ( 'statusMessageChanged' , this . get ( 'status_message' ) ) ;
}
} . bind ( this ) ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
constructPresence : function ( type , status _message ) {
var presence ;
2016-06-20 21:11:43 +02:00
type = typeof type === 'string' ? type : ( this . get ( 'status' ) || converse . default _state ) ;
status _message = typeof status _message === 'string' ? status _message : undefined ;
2016-03-16 12:49:35 +01:00
// Most of these presence types are actually not explicitly sent,
2016-06-20 21:11:43 +02:00
// but I add all of them here for reference and future proofing.
2016-03-16 12:49:35 +01:00
if ( ( type === 'unavailable' ) ||
( type === 'probe' ) ||
( type === 'error' ) ||
( type === 'unsubscribe' ) ||
( type === 'unsubscribed' ) ||
( type === 'subscribe' ) ||
( type === 'subscribed' ) ) {
presence = $pres ( { 'type' : type } ) ;
} else if ( type === 'offline' ) {
presence = $pres ( { 'type' : 'unavailable' } ) ;
2016-06-20 21:11:43 +02:00
} else if ( type === 'online' ) {
presence = $pres ( ) ;
2016-03-16 12:49:35 +01:00
} else {
2016-06-20 21:11:43 +02:00
presence = $pres ( ) . c ( 'show' ) . t ( type ) . up ( ) ;
}
if ( status _message ) {
presence . c ( 'status' ) . t ( status _message ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-16 12:49:35 +01:00
return presence ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
sendPresence : function ( type , status _message ) {
converse . connection . send ( this . constructPresence ( type , status _message ) ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
setStatus : function ( value ) {
this . sendPresence ( value ) ;
this . save ( { 'status' : value } ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
getStatus : function ( ) {
2016-06-20 21:11:43 +02:00
return this . get ( 'status' ) || converse . default _state ;
2016-03-07 18:54:07 +01:00
} ,
2016-03-16 12:49:35 +01:00
setStatusMessage : function ( status _message ) {
this . sendPresence ( this . getStatus ( ) , status _message ) ;
var prev _status = this . get ( 'status_message' ) ;
this . save ( { 'status_message' : status _message } ) ;
if ( this . xhr _custom _status ) {
$ . ajax ( {
url : this . xhr _custom _status _url ,
type : 'POST' ,
data : { 'msg' : status _message }
} ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-16 12:49:35 +01:00
if ( prev _status === status _message ) {
this . trigger ( "update-status-ui" , this ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-16 12:49:35 +01:00
}
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
this . Session = Backbone . Model ; // General session settings to be saved to sessionStorage.
this . Feature = Backbone . Model ;
this . Features = Backbone . Collection . extend ( {
/ * S e r v i c e D i s c o v e r y
2016-03-19 14:33:31 +01:00
* -- -- -- -- -- -- -- -- -
* 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
* /
2016-03-16 12:49:35 +01:00
model : converse . Feature ,
initialize : function ( ) {
this . addClientIdentities ( ) . addClientFeatures ( ) ;
this . browserStorage = new Backbone . BrowserStorage [ converse . storage ] (
b64 _sha1 ( 'converse.features' + converse . bare _jid ) ) ;
this . on ( 'add' , this . onFeatureAdded , this ) ;
if ( this . browserStorage . records . length === 0 ) {
// browserStorage is empty, so we've likely never queried this
// domain for features yet
converse . connection . disco . info ( converse . domain , null , this . onInfo . bind ( this ) ) ;
converse . connection . disco . items ( converse . domain , null , this . onItems . bind ( this ) ) ;
2016-02-26 18:28:18 +01:00
} else {
2016-03-16 12:49:35 +01:00
this . fetch ( { add : true } ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
onFeatureAdded : function ( feature ) {
converse . emit ( 'serviceDiscovered' , feature ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
addClientIdentities : function ( ) {
/* See http:/ / xmpp . org / registrar / disco - categories . html
* /
converse . connection . disco . addIdentity ( 'client' , 'web' , 'Converse.js' ) ;
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-03-16 12:49:35 +01:00
addClientFeatures : function ( ) {
/ * T h e s t r o p h e . d i s c o . j s p l u g i n k e e p s a l i s t o f f e a t u r e s w h i c h
* it will advertise to any # info queries made to it .
*
* See : http : //xmpp.org/extensions/xep-0030.html#info
* /
converse . connection . disco . addFeature ( Strophe . NS . BOSH ) ;
converse . connection . disco . addFeature ( Strophe . NS . CHATSTATES ) ;
converse . connection . disco . addFeature ( Strophe . NS . DISCO _INFO ) ;
converse . connection . disco . addFeature ( Strophe . NS . ROSTERX ) ; // Limited support
if ( converse . message _carbons ) {
converse . connection . disco . addFeature ( Strophe . NS . CARBONS ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
return this ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
onItems : function ( stanza ) {
$ ( stanza ) . find ( 'query item' ) . each ( function ( idx , item ) {
converse . connection . disco . info (
$ ( item ) . attr ( 'jid' ) ,
null ,
this . onInfo . bind ( this ) ) ;
} . bind ( this ) ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
onInfo : function ( stanza ) {
var $stanza = $ ( stanza ) ;
if ( ( $stanza . find ( 'identity[category=server][type=im]' ) . length === 0 ) &&
( $stanza . find ( 'identity[category=conference][type=text]' ) . length === 0 ) ) {
// This isn't an IM server component
2016-03-07 18:54:07 +01:00
return ;
2016-02-26 18:28:18 +01:00
}
2016-03-16 12:49:35 +01:00
$stanza . find ( 'feature' ) . each ( function ( idx , feature ) {
var namespace = $ ( feature ) . attr ( 'var' ) ;
this [ namespace ] = true ;
this . create ( {
'var' : namespace ,
'from' : $stanza . attr ( 'from' )
} ) ;
} . bind ( this ) ) ;
}
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
this . setUpXMLLogging = function ( ) {
2016-06-20 21:11:43 +02:00
Strophe . log = function ( level , msg ) {
converse . log ( msg , level ) ;
} ;
2016-03-16 12:49:35 +01:00
if ( this . debug ) {
2016-05-03 17:37:10 +02:00
this . connection . xmlInput = function ( body ) { converse . log ( body . outerHTML ) ; } ;
this . connection . xmlOutput = function ( body ) { converse . log ( body . outerHTML ) ; } ;
2016-03-16 12:49:35 +01:00
}
} ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
this . fetchLoginCredentials = function ( ) {
var deferred = new $ . Deferred ( ) ;
$ . ajax ( {
url : converse . credentials _url ,
type : 'GET' ,
dataType : "json" ,
success : function ( response ) {
deferred . resolve ( {
'jid' : response . jid ,
'password' : response . password
} ) ;
} ,
error : function ( response ) {
delete converse . connection ;
converse . emit ( 'noResumeableSession' ) ;
deferred . reject ( response ) ;
}
} ) ;
return deferred . promise ( ) ;
} ;
2016-03-16 12:49:35 +01:00
this . startNewBOSHSession = function ( ) {
$ . ajax ( {
url : this . prebind _url ,
type : 'GET' ,
2016-03-19 14:33:31 +01:00
dataType : "json" ,
2016-03-16 12:49:35 +01:00
success : function ( response ) {
this . connection . attach (
response . jid ,
response . sid ,
response . rid ,
this . onConnectStatusChanged
) ;
} . bind ( this ) ,
error : function ( response ) {
delete this . connection ;
this . emit ( 'noResumeableSession' ) ;
} . bind ( this )
} ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
this . attemptPreboundSession = function ( tokens ) {
/ * H a n d l e s e s s i o n r e s u m p t i o n o r i n i t i a l i z a t i o n w h e n p r e b i n d i s b e i n g u s e d .
* /
if ( this . jid && this . sid && this . rid ) {
return this . connection . attach ( this . jid , this . sid , this . rid , this . onConnectStatusChanged ) ;
} else if ( this . keepalive ) {
if ( ! this . jid ) {
2016-05-03 17:37:10 +02:00
throw new Error ( "attemptPreboundSession: when using 'keepalive' with 'prebind, " +
"you must supply the JID of the current user." ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-16 12:49:35 +01:00
try {
return this . connection . restore ( this . jid , this . onConnectStatusChanged ) ;
} catch ( e ) {
this . log ( "Could not restore session for jid: " + this . jid + " Error message: " + e . message ) ;
this . clearSession ( ) ; // If there's a roster, we want to clear it (see #555)
}
} else {
2016-05-03 17:37:10 +02:00
throw new Error ( "attemptPreboundSession: If you use prebind and not keepalive, " +
2016-03-16 12:49:35 +01:00
"then you MUST supply JID, RID and SID values" ) ;
}
// We haven't been able to attach yet. Let's see if there
// is a prebind_url, otherwise there's nothing with which
// we can attach.
if ( this . prebind _url ) {
this . startNewBOSHSession ( ) ;
} else {
delete this . connection ;
this . emit ( 'noResumeableSession' ) ;
}
} ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
this . autoLogin = function ( credentials ) {
if ( credentials ) {
2016-05-03 17:37:10 +02:00
// If passed in, then they come from credentials_url, so we
2016-04-01 15:33:14 +02:00
// set them on the converse object.
this . jid = credentials . jid ;
this . password = credentials . password ;
}
if ( this . authentication === converse . ANONYMOUS ) {
2016-07-26 08:00:30 +02:00
if ( ! this . jid ) {
throw new Error ( "Config Error: when using anonymous login " +
"you need to provide the server's domain via the 'jid' option. " +
"Either when calling converse.initialize, or when calling " +
"converse.user.login." ) ;
}
2016-04-01 15:33:14 +02:00
this . connection . connect ( this . jid . toLowerCase ( ) , null , this . onConnectStatusChanged ) ;
} else if ( this . authentication === converse . LOGIN ) {
2016-09-16 14:35:02 +02:00
var password = converse . connection . pass || this . password ;
if ( ! password ) {
if ( this . auto _login && ! this . password ) {
throw new Error ( "initConnection: If you use auto_login and " +
"authentication='login' then you also need to provide a password." ) ;
}
converse . disconnection _cause = Strophe . Status . AUTHFAIL ;
converse . onDisconnected ( ) ;
converse . giveFeedback ( '' ) ; // Wipe the feedback
2016-04-01 15:33:14 +02:00
}
var resource = Strophe . getResourceFromJid ( this . jid ) ;
if ( ! resource ) {
this . jid = this . jid . toLowerCase ( ) + converse . generateResource ( ) ;
} else {
this . jid = Strophe . getBareJidFromJid ( this . jid ) . toLowerCase ( ) + '/' + resource ;
}
2016-09-16 14:35:02 +02:00
this . connection . connect ( this . jid , password , this . onConnectStatusChanged ) ;
2016-04-01 15:33:14 +02:00
}
} ;
2016-03-16 12:49:35 +01:00
this . attemptNonPreboundSession = function ( ) {
/ * H a n d l e s e s s i o n r e s u m p t i o n o r i n i t i a l i z a t i o n w h e n p r e b i n d i s n o t b e i n g u s e d .
*
* Two potential options exist and are handled in this method :
* 1. keepalive
* 2. auto _login
* /
if ( this . keepalive ) {
try {
2016-06-20 21:11:43 +02:00
return this . connection . restore ( this . jid , this . onConnectStatusChanged ) ;
2016-03-16 12:49:35 +01:00
} catch ( e ) {
this . log ( "Could not restore session. Error message: " + e . message ) ;
this . clearSession ( ) ; // If there's a roster, we want to clear it (see #555)
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
}
if ( this . auto _login ) {
2016-04-01 15:33:14 +02:00
if ( this . credentials _url ) {
this . fetchLoginCredentials ( ) . done ( this . autoLogin . bind ( this ) ) ;
} else if ( ! this . jid ) {
throw new Error (
"initConnection: If you use auto_login, you also need" +
"to give either a jid value (and if applicable a " +
"password) or you need to pass in a URL from where the " +
"username and password can be fetched (via credentials_url)."
) ;
} else {
this . autoLogin ( ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
}
} ;
2016-02-28 10:00:22 +01:00
2016-05-03 17:37:10 +02:00
this . logIn = function ( credentials ) {
2016-07-26 08:00:30 +02:00
if ( credentials || this . authentication === converse . ANONYMOUS ) {
2016-05-03 17:37:10 +02:00
// When credentials are passed in, they override prebinding
// or credentials fetching via HTTP
this . autoLogin ( credentials ) ;
2016-03-16 12:49:35 +01:00
} else {
// We now try to resume or automatically set up a new session.
// Otherwise the user will be shown a login form.
if ( this . authentication === converse . PREBIND ) {
this . attemptPreboundSession ( ) ;
} else {
this . attemptNonPreboundSession ( ) ;
2016-03-07 18:54:07 +01:00
}
}
2016-03-16 12:49:35 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-05-03 17:37:10 +02:00
this . initConnection = function ( ) {
if ( this . connection ) {
return ;
}
if ( ! this . bosh _service _url && ! this . websocket _url ) {
throw new Error ( "initConnection: you must supply a value for either the bosh_service_url or websocket_url or both." ) ;
}
if ( ( 'WebSocket' in window || 'MozWebSocket' in window ) && this . websocket _url ) {
this . connection = new Strophe . Connection ( this . websocket _url ) ;
} else if ( this . bosh _service _url ) {
this . connection = new Strophe . Connection ( this . bosh _service _url , { 'keepalive' : this . keepalive } ) ;
} else {
throw new Error ( "initConnection: this browser does not support websockets and bosh_service_url wasn't specified." ) ;
}
} ;
2016-03-16 12:49:35 +01:00
this . _tearDown = function ( ) {
/ * R e m o v e t h o s e v i e w s w h i c h a r e o n l y a l l o w e d w i t h a v a l i d
* connection .
* /
2016-07-26 08:00:30 +02:00
this . unregisterPresenceHandler ( ) ;
2016-03-16 12:49:35 +01:00
if ( this . roster ) {
this . roster . off ( ) . reset ( ) ; // Removes roster contacts
}
this . chatboxes . remove ( ) ; // Don't call off(), events won't get re-registered upon reconnect.
if ( this . features ) {
this . features . reset ( ) ;
}
2016-05-03 17:37:10 +02:00
$ ( window ) . off ( 'click mousemove keypress focus' + unloadevent , converse . onUserActivity ) ;
window . clearInterval ( converse . everySecondTrigger ) ;
2016-03-16 12:49:35 +01:00
return this ;
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
this . _initialize = function ( ) {
this . chatboxes = new this . ChatBoxes ( ) ;
this . chatboxviews = new this . ChatBoxViews ( { model : this . chatboxes } ) ;
this . initSession ( ) ;
this . initConnection ( ) ;
2016-05-03 17:37:10 +02:00
this . setUpXMLLogging ( ) ;
this . logIn ( ) ;
2016-03-16 12:49:35 +01:00
return this ;
} ;
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
// Initialization
// --------------
// This is the end of the initialize method.
if ( settings . connection ) {
this . connection = settings . connection ;
}
2016-06-20 21:11:43 +02:00
var updateSettings = function ( settings ) {
/ * H e l p e r m e t h o d w h i c h g e t s p u t o n t h e p l u g i n a n d a l l o w s i t t o
* add more user - facing config settings to converse . js .
* /
_ . extend ( converse . default _settings , settings ) ;
_ . extend ( converse , settings ) ;
_ . extend ( converse , _ . pick ( converse . user _settings , Object . keys ( settings ) ) ) ;
} ;
converse . pluggable . initializePlugins ( {
'updateSettings' : updateSettings ,
'converse' : converse
} ) ;
converse . emit ( 'pluginsInitialized' ) ;
converse . _initialize ( ) ;
converse . registerGlobalEventHandlers ( ) ;
return init _deferred . promise ( ) ;
2016-03-16 12:49:35 +01:00
} ;
return converse ;
} ) ) ;
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
/ * T h i s i s a C o n v e r s e . j s p l u g i n w h i c h a d d s u p p o r t f o r m u l t i - u s e r c h a t r o o m s , a s
* specified in XEP - 0045 Multi - user chat .
* /
( function ( root , factory ) {
define ( "converse-api" , [
"jquery" ,
"underscore" ,
"moment_with_locales" ,
"strophe" ,
"utils" ,
"converse-core"
] ,
factory ) ;
} ( this , function ( $ , _ , moment , strophe , utils , converse ) {
var Strophe = strophe . Strophe ;
return {
'initialize' : function ( settings , callback ) {
2016-06-20 21:11:43 +02:00
return converse . initialize ( settings , callback ) ;
2016-03-16 12:49:35 +01:00
} ,
2016-06-20 21:11:43 +02:00
'log' : converse . log ,
2016-03-30 14:03:15 +02:00
'connection' : {
'connected' : function ( ) {
return converse . connection && converse . connection . connected || false ;
} ,
'disconnect' : function ( ) {
converse . connection . disconnect ( ) ;
} ,
2016-03-16 12:49:35 +01:00
} ,
'user' : {
2016-05-24 10:23:19 +02:00
'jid' : function ( ) {
return converse . connection . jid ;
} ,
2016-05-03 17:37:10 +02:00
'login' : function ( credentials ) {
converse . initConnection ( ) ;
converse . logIn ( credentials ) ;
} ,
2016-03-16 12:49:35 +01:00
'logout' : function ( ) {
converse . logOut ( ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
'status' : {
'get' : function ( ) {
return converse . xmppstatus . get ( 'status' ) ;
} ,
'set' : function ( value , message ) {
var data = { 'status' : value } ;
if ( ! _ . contains ( _ . keys ( converse . STATUS _WEIGHTS ) , value ) ) {
throw new Error ( 'Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1' ) ;
}
if ( typeof message === "string" ) {
data . status _message = message ;
}
2016-03-19 14:33:31 +01:00
converse . xmppstatus . sendPresence ( value ) ;
2016-03-16 12:49:35 +01:00
converse . xmppstatus . save ( data ) ;
} ,
'message' : {
'get' : function ( ) {
return converse . xmppstatus . get ( 'status_message' ) ;
} ,
'set' : function ( stat ) {
converse . xmppstatus . save ( { 'status_message' : stat } ) ;
2016-02-28 10:00:22 +01:00
}
}
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
} ,
'settings' : {
'get' : function ( key ) {
if ( _ . contains ( Object . keys ( converse . default _settings ) , key ) ) {
return converse [ key ] ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
} ,
'set' : function ( key , val ) {
var o = { } ;
if ( typeof key === "object" ) {
_ . extend ( converse , _ . pick ( key , Object . keys ( converse . default _settings ) ) ) ;
} else if ( typeof key === "string" ) {
o [ key ] = val ;
_ . extend ( converse , _ . pick ( o , Object . keys ( converse . default _settings ) ) ) ;
2016-03-07 18:54:07 +01:00
}
2016-02-28 10:00:22 +01:00
}
2016-03-16 12:49:35 +01:00
} ,
'contacts' : {
'get' : function ( jids ) {
var _transform = function ( jid ) {
var contact = converse . roster . get ( Strophe . getBareJidFromJid ( jid ) ) ;
if ( contact ) {
return contact . attributes ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return null ;
} ;
if ( typeof jids === "undefined" ) {
jids = converse . roster . pluck ( 'jid' ) ;
} else if ( typeof jids === "string" ) {
return _transform ( jids ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-16 12:49:35 +01:00
return _ . map ( jids , _transform ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
'add' : function ( jid , name ) {
if ( typeof jid !== "string" || jid . indexOf ( '@' ) < 0 ) {
throw new TypeError ( 'contacts.add: invalid jid' ) ;
}
converse . roster . addAndSubscribe ( jid , _ . isEmpty ( name ) ? jid : name ) ;
}
} ,
'chats' : {
'open' : function ( jids ) {
var chatbox ;
if ( typeof jids === "undefined" ) {
converse . log ( "chats.open: You need to provide at least one JID" , "error" ) ;
return null ;
} else if ( typeof jids === "string" ) {
chatbox = converse . wrappedChatBox ( converse . chatboxes . getChatBox ( jids , true ) ) ;
return chatbox ;
}
return _ . map ( jids , function ( jid ) {
chatbox = converse . wrappedChatBox ( converse . chatboxes . getChatBox ( jid , true ) ) ;
return chatbox ;
} ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-16 12:49:35 +01:00
'get' : function ( jids ) {
if ( typeof jids === "undefined" ) {
2016-07-26 08:00:30 +02:00
var result = [ ] ;
converse . chatboxes . each ( function ( chatbox ) {
// FIXME: Leaky abstraction from MUC. We need to add a
// base type for chat boxes, and check for that.
if ( chatbox . get ( 'type' ) !== 'chatroom' ) {
result . push ( converse . wrappedChatBox ( chatbox ) ) ;
}
} ) ;
return result ;
2016-03-16 12:49:35 +01:00
} else if ( typeof jids === "string" ) {
2016-08-12 22:52:21 +02:00
return converse . wrappedChatBox ( converse . chatboxes . getChatBox ( jids ) ) ;
2016-03-16 12:49:35 +01:00
}
2016-06-20 21:11:43 +02:00
return _ . map ( jids ,
_ . partial (
_ . compose (
converse . wrappedChatBox . bind ( converse ) , converse . chatboxes . getChatBox . bind ( converse . chatboxes )
) , _ , true
)
) ;
2016-03-16 12:49:35 +01:00
}
} ,
'tokens' : {
'get' : function ( id ) {
if ( ! converse . expose _rid _and _sid || typeof converse . connection === "undefined" ) {
return null ;
}
if ( id . toLowerCase ( ) === 'rid' ) {
return converse . connection . rid || converse . connection . _proto . rid ;
} else if ( id . toLowerCase ( ) === 'sid' ) {
return converse . connection . sid || converse . connection . _proto . sid ;
}
}
} ,
'listen' : {
2016-09-21 17:24:32 +02:00
'once' : function ( evt , handler , context ) {
converse . once ( evt , handler , context ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-09-21 17:24:32 +02:00
'on' : function ( evt , handler , context ) {
converse . on ( evt , handler , context ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
'not' : function ( evt , handler ) {
converse . off ( evt , handler ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-19 14:33:31 +01:00
'stanza' : function ( name , options , handler ) {
if ( typeof options === 'function' ) {
handler = options ;
options = { } ;
} else {
options = options || { } ;
}
converse . connection . addHandler (
handler ,
options . ns ,
2016-03-30 14:03:15 +02:00
name ,
2016-03-19 14:33:31 +01:00
options . type ,
options . id ,
options . from ,
options
) ;
} ,
2016-03-16 12:49:35 +01:00
} ,
'send' : function ( stanza ) {
converse . connection . send ( stanza ) ;
} ,
'plugins' : {
'add' : function ( name , plugin ) {
2016-06-20 21:11:43 +02:00
plugin . _ _name _ _ = name ;
converse . pluggable . plugins [ name ] = plugin ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-16 12:49:35 +01:00
'remove' : function ( name ) {
delete converse . plugins [ name ] ;
2016-02-26 18:28:18 +01:00
} ,
2016-03-16 12:49:35 +01:00
'override' : function ( name , value ) {
/ * H e l p e r m e t h o d f o r o v e r r i d i n g m e t h o d s a n d a t t r i b u t e s d i r e c t l y o n t h e
* converse object . For Backbone objects , use instead the 'extend'
* method .
*
* If a method is overridden , then the original method will still be
2016-09-16 14:35:02 +02:00
* available via the _ _super _ _ attribute .
2016-03-16 12:49:35 +01:00
*
* name : The attribute being overridden .
* value : The value of the attribute being overridden .
* /
converse . _overrideAttribute ( name , value ) ;
2016-02-28 10:00:22 +01:00
} ,
2016-03-16 12:49:35 +01:00
'extend' : function ( obj , attributes ) {
/ * H e l p e r m e t h o d f o r o v e r r i d i n g o r e x t e n d i n g C o n v e r s e ' s B a c k b o n e V i e w s o r M o d e l s
*
* When a method is overriden , the original will still be available
2016-09-16 14:35:02 +02:00
* on the _ _super _ _ attribute of the object being overridden .
2016-03-16 12:49:35 +01:00
*
* obj : The Backbone View or Model
* attributes : A hash of attributes , such as you would pass to Backbone . Model . extend or Backbone . View . extend
* /
converse . _extendObject ( obj , attributes ) ;
}
} ,
'env' : {
'$build' : strophe . $build ,
'$iq' : strophe . $iq ,
'$msg' : strophe . $msg ,
'$pres' : strophe . $pres ,
'Strophe' : strophe . Strophe ,
'b64_sha1' : strophe . SHA1 . b64 _sha1 ,
'_' : _ ,
'jQuery' : $ ,
'moment' : moment ,
'utils' : utils
}
} ;
} ) ) ;
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
/ *
2016-05-03 17:37:10 +02:00
jed . js
v0 . 5.0 beta
https : //github.com/SlexAxton/Jed
2016-03-16 12:49:35 +01:00
-- -- -- -- -- -
A gettext compatible i18n library for modern JavaScript Applications
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
by Alex Sexton - AlexSexton [ at ] gmail - @ SlexAxton
WTFPL license for use
Dojo CLA for contributions
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
Jed offers the entire applicable GNU gettext spec ' d set of
functions , but also offers some nicer wrappers around them .
The api for gettext was written for a language with no function
overloading , so Jed allows a little more of that .
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
Many thanks to Joshua I . Miller - unrtst @ cpan . org - who wrote
gettext . js back in 2008. I was able to vet a lot of my ideas
against his . I also made sure Jed passed against his tests
in order to offer easy upgrades -- jsgettext . berlios . de
* /
( function ( root , undef ) {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// Set up some underscore-style functions, if you already have
// underscore, feel free to delete this section, and use it
// directly, however, the amount of functions used doesn't
// warrant having underscore as a full dependency.
// Underscore 1.3.0 was used to port and is licensed
// under the MIT License by Jeremy Ashkenas.
var ArrayProto = Array . prototype ,
ObjProto = Object . prototype ,
slice = ArrayProto . slice ,
hasOwnProp = ObjProto . hasOwnProperty ,
nativeForEach = ArrayProto . forEach ,
breaker = { } ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// We're not using the OOP style _ so we don't need the
// extra level of indirection. This still means that you
// sub out for real `_` though.
var _ = {
forEach : function ( obj , iterator , context ) {
var i , l , key ;
if ( obj === null ) {
return ;
}
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
if ( nativeForEach && obj . forEach === nativeForEach ) {
obj . forEach ( iterator , context ) ;
}
else if ( obj . length === + obj . length ) {
for ( i = 0 , l = obj . length ; i < l ; i ++ ) {
if ( i in obj && iterator . call ( context , obj [ i ] , i , obj ) === breaker ) {
return ;
}
}
}
else {
for ( key in obj ) {
if ( hasOwnProp . call ( obj , key ) ) {
if ( iterator . call ( context , obj [ key ] , key , obj ) === breaker ) {
return ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
}
}
}
} ,
extend : function ( obj ) {
this . forEach ( slice . call ( arguments , 1 ) , function ( source ) {
for ( var prop in source ) {
obj [ prop ] = source [ prop ] ;
}
} ) ;
return obj ;
}
} ;
// END Miniature underscore impl
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Jed is a constructor function
var Jed = function ( options ) {
// Some minimal defaults
this . defaults = {
"locale_data" : {
"messages" : {
"" : {
"domain" : "messages" ,
"lang" : "en" ,
"plural_forms" : "nplurals=2; plural=(n != 1);"
}
// There are no default keys, though
}
} ,
// The default domain if one is missing
2016-05-03 17:37:10 +02:00
"domain" : "messages"
2016-03-16 12:49:35 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Mix in the sent options with the default options
this . options = _ . extend ( { } , this . defaults , options ) ;
this . textdomain ( this . options . domain ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
if ( options . domain && ! this . options . locale _data [ this . options . domain ] ) {
throw new Error ( 'Text domain set to non-existent domain: `' + options . domain + '`' ) ;
}
} ;
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
// The gettext spec sets this character as the default
// delimiter for context lookups.
// e.g.: context\u0004key
// If your translation company uses something different,
// just change this at any time and it will use that instead.
Jed . context _delimiter = String . fromCharCode ( 4 ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
function getPluralFormFunc ( plural _form _string ) {
return Jed . PF . compile ( plural _form _string || "nplurals=2; plural=(n != 1);" ) ;
}
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
function Chain ( key , i18n ) {
this . _key = key ;
this . _i18n = i18n ;
}
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Create a chainable api for adding args prettily
_ . extend ( Chain . prototype , {
onDomain : function ( domain ) {
this . _domain = domain ;
return this ;
} ,
withContext : function ( context ) {
this . _context = context ;
return this ;
} ,
ifPlural : function ( num , pkey ) {
this . _val = num ;
this . _pkey = pkey ;
return this ;
} ,
fetch : function ( sArr ) {
if ( { } . toString . call ( sArr ) != '[object Array]' ) {
2016-05-03 17:37:10 +02:00
sArr = [ ] . slice . call ( arguments ) ;
2016-03-16 12:49:35 +01:00
}
return ( sArr && sArr . length ? Jed . sprintf : function ( x ) { return x ; } ) (
this . _i18n . dcnpgettext ( this . _domain , this . _context , this . _key , this . _pkey , this . _val ) ,
sArr
) ;
}
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Add functions to the Jed prototype.
// These will be the functions on the object that's returned
// from creating a `new Jed()`
// These seem redundant, but they gzip pretty well.
_ . extend ( Jed . prototype , {
// The sexier api start point
translate : function ( key ) {
return new Chain ( key , this ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
textdomain : function ( domain ) {
if ( ! domain ) {
return this . _textdomain ;
}
this . _textdomain = domain ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
gettext : function ( key ) {
return this . dcnpgettext . call ( this , undef , undef , key ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
dgettext : function ( domain , key ) {
return this . dcnpgettext . call ( this , domain , undef , key ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
dcgettext : function ( domain , key /*, category */ ) {
// Ignores the category anyways
return this . dcnpgettext . call ( this , domain , undef , key ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
ngettext : function ( skey , pkey , val ) {
return this . dcnpgettext . call ( this , undef , undef , skey , pkey , val ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
dngettext : function ( domain , skey , pkey , val ) {
return this . dcnpgettext . call ( this , domain , undef , skey , pkey , val ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
dcngettext : function ( domain , skey , pkey , val /*, category */ ) {
return this . dcnpgettext . call ( this , domain , undef , skey , pkey , val ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
pgettext : function ( context , key ) {
return this . dcnpgettext . call ( this , undef , context , key ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
dpgettext : function ( domain , context , key ) {
return this . dcnpgettext . call ( this , domain , context , key ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
dcpgettext : function ( domain , context , key /*, category */ ) {
return this . dcnpgettext . call ( this , domain , context , key ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
npgettext : function ( context , skey , pkey , val ) {
return this . dcnpgettext . call ( this , undef , context , skey , pkey , val ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
dnpgettext : function ( domain , context , skey , pkey , val ) {
return this . dcnpgettext . call ( this , domain , context , skey , pkey , val ) ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
// The most fully qualified gettext function. It has every option.
// Since it has every option, we can use it from every other method.
// This is the bread and butter.
// Technically there should be one more argument in this function for 'Category',
// but since we never use it, we might as well not waste the bytes to define it.
dcnpgettext : function ( domain , context , singular _key , plural _key , val ) {
// Set some defaults
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
plural _key = plural _key || singular _key ;
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
// Use the global domain default if one
// isn't explicitly passed in
domain = domain || this . _textdomain ;
2016-02-26 18:28:18 +01:00
2016-05-03 17:37:10 +02:00
// Default the value to the singular case
val = typeof val == 'undefined' ? 1 : val ;
2016-03-16 12:49:35 +01:00
var fallback ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Handle special cases
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// No options found
if ( ! this . options ) {
// There's likely something wrong, but we'll return the correct key for english
// We do this by instantiating a brand new Jed instance with the default set
// for everything that could be broken.
fallback = new Jed ( ) ;
return fallback . dcnpgettext . call ( fallback , undefined , undefined , singular _key , plural _key , val ) ;
}
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// No translation data provided
if ( ! this . options . locale _data ) {
throw new Error ( 'No locale data provided.' ) ;
}
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
if ( ! this . options . locale _data [ domain ] ) {
throw new Error ( 'Domain `' + domain + '` was not found.' ) ;
}
if ( ! this . options . locale _data [ domain ] [ "" ] ) {
throw new Error ( 'No locale meta information provided.' ) ;
}
// Make sure we have a truthy key. Otherwise we might start looking
// into the empty string key, which is the options for the locale
// data.
if ( ! singular _key ) {
throw new Error ( 'No translation key found.' ) ;
}
2016-05-03 17:37:10 +02:00
// Handle invalid numbers, but try casting strings for good measure
if ( typeof val != 'number' ) {
val = parseInt ( val , 10 ) ;
if ( isNaN ( val ) ) {
throw new Error ( 'The number that was passed in is not a number.' ) ;
}
}
2016-03-16 12:49:35 +01:00
var key = context ? context + Jed . context _delimiter + singular _key : singular _key ,
locale _data = this . options . locale _data ,
dict = locale _data [ domain ] ,
2016-05-03 17:37:10 +02:00
pluralForms = dict [ "" ] . plural _forms || ( locale _data . messages || this . defaults . locale _data . messages ) [ "" ] . plural _forms ,
val _idx = getPluralFormFunc ( pluralForms ) ( val ) + 1 ,
2016-03-16 12:49:35 +01:00
val _list ,
res ;
// Throw an error if a domain isn't found
if ( ! dict ) {
throw new Error ( 'No domain named `' + domain + '` could be found.' ) ;
}
val _list = dict [ key ] ;
// If there is no match, then revert back to
// english style singular/plural with the keys passed in.
2016-05-03 17:37:10 +02:00
if ( ! val _list || val _idx >= val _list . length ) {
2016-03-16 12:49:35 +01:00
if ( this . options . missing _key _callback ) {
2016-05-03 17:37:10 +02:00
this . options . missing _key _callback ( key ) ;
2016-03-29 08:56:13 +02:00
}
2016-05-03 17:37:10 +02:00
res = [ null , singular _key , plural _key ] ;
return res [ getPluralFormFunc ( pluralForms ) ( val ) + 1 ] ;
2016-03-16 12:49:35 +01:00
}
res = val _list [ val _idx ] ;
// This includes empty strings on purpose
if ( ! res ) {
2016-05-03 17:37:10 +02:00
res = [ null , singular _key , plural _key ] ;
return res [ getPluralFormFunc ( pluralForms ) ( val ) + 1 ] ;
2016-03-16 12:49:35 +01:00
}
return res ;
}
} ) ;
// We add in sprintf capabilities for post translation value interolation
// This is not internally used, so you can remove it if you have this
// available somewhere else, or want to use a different system.
// We _slightly_ modify the normal sprintf behavior to more gracefully handle
// undefined values.
/ * *
sprintf ( ) for JavaScript 0.7 - beta1
http : //www.diveintojavascript.com/projects/javascript-sprintf
Copyright ( c ) Alexandru Marasteanu < alexaholic [ at ) gmail ( dot ] com >
All rights reserved .
Redistribution and use in source and binary forms , with or without
modification , are permitted provided that the following conditions are met :
* Redistributions of source code must retain the above copyright
notice , this list of conditions and the following disclaimer .
* Redistributions in binary form must reproduce the above copyright
notice , this list of conditions and the following disclaimer in the
documentation and / or other materials provided with the distribution .
* Neither the name of sprintf ( ) for JavaScript nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED . IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* /
var sprintf = ( function ( ) {
function get _type ( variable ) {
return Object . prototype . toString . call ( variable ) . slice ( 8 , - 1 ) . toLowerCase ( ) ;
}
function str _repeat ( input , multiplier ) {
for ( var output = [ ] ; multiplier > 0 ; output [ -- multiplier ] = input ) { /* do nothing */ }
return output . join ( '' ) ;
}
var str _format = function ( ) {
if ( ! str _format . cache . hasOwnProperty ( arguments [ 0 ] ) ) {
str _format . cache [ arguments [ 0 ] ] = str _format . parse ( arguments [ 0 ] ) ;
}
return str _format . format . call ( null , str _format . cache [ arguments [ 0 ] ] , arguments ) ;
} ;
str _format . format = function ( parse _tree , argv ) {
var cursor = 1 , tree _length = parse _tree . length , node _type = '' , arg , output = [ ] , i , k , match , pad , pad _character , pad _length ;
for ( i = 0 ; i < tree _length ; i ++ ) {
node _type = get _type ( parse _tree [ i ] ) ;
if ( node _type === 'string' ) {
output . push ( parse _tree [ i ] ) ;
}
else if ( node _type === 'array' ) {
match = parse _tree [ i ] ; // convenience purposes only
if ( match [ 2 ] ) { // keyword argument
arg = argv [ cursor ] ;
for ( k = 0 ; k < match [ 2 ] . length ; k ++ ) {
if ( ! arg . hasOwnProperty ( match [ 2 ] [ k ] ) ) {
throw ( sprintf ( '[sprintf] property "%s" does not exist' , match [ 2 ] [ k ] ) ) ;
}
arg = arg [ match [ 2 ] [ k ] ] ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
}
else if ( match [ 1 ] ) { // positional argument (explicit)
arg = argv [ match [ 1 ] ] ;
}
else { // positional argument (implicit)
arg = argv [ cursor ++ ] ;
}
if ( /[^s]/ . test ( match [ 8 ] ) && ( get _type ( arg ) != 'number' ) ) {
throw ( sprintf ( '[sprintf] expecting number but found %s' , get _type ( arg ) ) ) ;
}
// Jed EDIT
if ( typeof arg == 'undefined' || arg === null ) {
arg = '' ;
}
// Jed EDIT
switch ( match [ 8 ] ) {
case 'b' : arg = arg . toString ( 2 ) ; break ;
case 'c' : arg = String . fromCharCode ( arg ) ; break ;
case 'd' : arg = parseInt ( arg , 10 ) ; break ;
case 'e' : arg = match [ 7 ] ? arg . toExponential ( match [ 7 ] ) : arg . toExponential ( ) ; break ;
case 'f' : arg = match [ 7 ] ? parseFloat ( arg ) . toFixed ( match [ 7 ] ) : parseFloat ( arg ) ; break ;
case 'o' : arg = arg . toString ( 8 ) ; break ;
case 's' : arg = ( ( arg = String ( arg ) ) && match [ 7 ] ? arg . substring ( 0 , match [ 7 ] ) : arg ) ; break ;
case 'u' : arg = Math . abs ( arg ) ; break ;
case 'x' : arg = arg . toString ( 16 ) ; break ;
case 'X' : arg = arg . toString ( 16 ) . toUpperCase ( ) ; break ;
}
arg = ( /[def]/ . test ( match [ 8 ] ) && match [ 3 ] && arg >= 0 ? '+' + arg : arg ) ;
pad _character = match [ 4 ] ? match [ 4 ] == '0' ? '0' : match [ 4 ] . charAt ( 1 ) : ' ' ;
pad _length = match [ 6 ] - String ( arg ) . length ;
pad = match [ 6 ] ? str _repeat ( pad _character , pad _length ) : '' ;
output . push ( match [ 5 ] ? arg + pad : pad + arg ) ;
}
}
return output . join ( '' ) ;
} ;
str _format . cache = { } ;
str _format . parse = function ( fmt ) {
var _fmt = fmt , match = [ ] , parse _tree = [ ] , arg _names = 0 ;
while ( _fmt ) {
if ( ( match = /^[^\x25]+/ . exec ( _fmt ) ) !== null ) {
parse _tree . push ( match [ 0 ] ) ;
}
else if ( ( match = /^\x25{2}/ . exec ( _fmt ) ) !== null ) {
parse _tree . push ( '%' ) ;
}
else if ( ( match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/ . exec ( _fmt ) ) !== null ) {
if ( match [ 2 ] ) {
arg _names |= 1 ;
var field _list = [ ] , replacement _field = match [ 2 ] , field _match = [ ] ;
if ( ( field _match = /^([a-z_][a-z_\d]*)/i . exec ( replacement _field ) ) !== null ) {
field _list . push ( field _match [ 1 ] ) ;
while ( ( replacement _field = replacement _field . substring ( field _match [ 0 ] . length ) ) !== '' ) {
if ( ( field _match = /^\.([a-z_][a-z_\d]*)/i . exec ( replacement _field ) ) !== null ) {
field _list . push ( field _match [ 1 ] ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
else if ( ( field _match = /^\[(\d+)\]/ . exec ( replacement _field ) ) !== null ) {
field _list . push ( field _match [ 1 ] ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
else {
throw ( '[sprintf] huh?' ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
}
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
else {
throw ( '[sprintf] huh?' ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
match [ 2 ] = field _list ;
}
else {
arg _names |= 2 ;
}
if ( arg _names === 3 ) {
throw ( '[sprintf] mixing positional and named placeholders is not (yet) supported' ) ;
}
parse _tree . push ( match ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
else {
throw ( '[sprintf] huh?' ) ;
}
_fmt = _fmt . substring ( match [ 0 ] . length ) ;
}
return parse _tree ;
2016-03-07 18:54:07 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
return str _format ;
} ) ( ) ;
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
var vsprintf = function ( fmt , argv ) {
argv . unshift ( fmt ) ;
return sprintf . apply ( null , argv ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
Jed . parse _plural = function ( plural _forms , n ) {
plural _forms = plural _forms . replace ( /n/g , n ) ;
return Jed . parse _expression ( plural _forms ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
Jed . sprintf = function ( fmt , args ) {
if ( { } . toString . call ( args ) == '[object Array]' ) {
return vsprintf ( fmt , [ ] . slice . call ( args ) ) ;
}
return sprintf . apply ( this , [ ] . slice . call ( arguments ) ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
Jed . prototype . sprintf = function ( ) {
return Jed . sprintf . apply ( this , arguments ) ;
} ;
// END sprintf Implementation
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Start the Plural forms section
// This is a full plural form expression parser. It is used to avoid
// running 'eval' or 'new Function' directly against the plural
// forms.
//
// This can be important if you get translations done through a 3rd
// party vendor. I encourage you to use this instead, however, I
// also will provide a 'precompiler' that you can use at build time
// to output valid/safe function representations of the plural form
// expressions. This means you can build this code out for the most
// part.
Jed . PF = { } ;
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
Jed . PF . parse = function ( p ) {
var plural _str = Jed . PF . extractPluralExpr ( p ) ;
return Jed . PF . parser . parse . call ( Jed . PF . parser , plural _str ) ;
} ;
Jed . PF . compile = function ( p ) {
// Handle trues and falses as 0 and 1
function imply ( val ) {
return ( val === true ? 1 : val ? val : 0 ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
var ast = Jed . PF . parse ( p ) ;
return function ( n ) {
return imply ( Jed . PF . interpreter ( ast ) ( n ) ) ;
} ;
2016-03-07 18:54:07 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
Jed . PF . interpreter = function ( ast ) {
return function ( n ) {
var res ;
switch ( ast . type ) {
case 'GROUP' :
return Jed . PF . interpreter ( ast . expr ) ( n ) ;
case 'TERNARY' :
if ( Jed . PF . interpreter ( ast . expr ) ( n ) ) {
return Jed . PF . interpreter ( ast . truthy ) ( n ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return Jed . PF . interpreter ( ast . falsey ) ( n ) ;
case 'OR' :
return Jed . PF . interpreter ( ast . left ) ( n ) || Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'AND' :
return Jed . PF . interpreter ( ast . left ) ( n ) && Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'LT' :
return Jed . PF . interpreter ( ast . left ) ( n ) < Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'GT' :
return Jed . PF . interpreter ( ast . left ) ( n ) > Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'LTE' :
return Jed . PF . interpreter ( ast . left ) ( n ) <= Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'GTE' :
return Jed . PF . interpreter ( ast . left ) ( n ) >= Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'EQ' :
return Jed . PF . interpreter ( ast . left ) ( n ) == Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'NEQ' :
return Jed . PF . interpreter ( ast . left ) ( n ) != Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'MOD' :
return Jed . PF . interpreter ( ast . left ) ( n ) % Jed . PF . interpreter ( ast . right ) ( n ) ;
case 'VAR' :
return n ;
case 'NUM' :
return ast . val ;
default :
throw new Error ( "Invalid Token found." ) ;
}
2016-03-07 18:54:07 +01:00
} ;
2016-03-16 12:49:35 +01:00
} ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
Jed . PF . extractPluralExpr = function ( p ) {
// trim first
p = p . replace ( /^\s\s*/ , '' ) . replace ( /\s\s*$/ , '' ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
if ( ! /;\s*$/ . test ( p ) ) {
p = p . concat ( ';' ) ;
2016-03-07 18:54:07 +01:00
}
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
var nplurals _re = /nplurals\=(\d+);/ ,
plural _re = /plural\=(.*);/ ,
nplurals _matches = p . match ( nplurals _re ) ,
res = { } ,
plural _matches ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Find the nplurals number
if ( nplurals _matches . length > 1 ) {
res . nplurals = nplurals _matches [ 1 ] ;
}
else {
throw new Error ( 'nplurals not found in plural_forms string: ' + p ) ;
}
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// remove that data to get to the formula
p = p . replace ( nplurals _re , "" ) ;
plural _matches = p . match ( plural _re ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
if ( ! ( plural _matches && plural _matches . length > 1 ) ) {
throw new Error ( '`plural` expression not found: ' + p ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return plural _matches [ 1 ] ;
} ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
/* Jison generated parser */
Jed . PF . parser = ( function ( ) {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
var parser = { trace : function trace ( ) { } ,
yy : { } ,
symbols _ : { "error" : 2 , "expressions" : 3 , "e" : 4 , "EOF" : 5 , "?" : 6 , ":" : 7 , "||" : 8 , "&&" : 9 , "<" : 10 , "<=" : 11 , ">" : 12 , ">=" : 13 , "!=" : 14 , "==" : 15 , "%" : 16 , "(" : 17 , ")" : 18 , "n" : 19 , "NUMBER" : 20 , "$accept" : 0 , "$end" : 1 } ,
terminals _ : { 2 : "error" , 5 : "EOF" , 6 : "?" , 7 : ":" , 8 : "||" , 9 : "&&" , 10 : "<" , 11 : "<=" , 12 : ">" , 13 : ">=" , 14 : "!=" , 15 : "==" , 16 : "%" , 17 : "(" , 18 : ")" , 19 : "n" , 20 : "NUMBER" } ,
productions _ : [ 0 , [ 3 , 2 ] , [ 4 , 5 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 3 ] , [ 4 , 1 ] , [ 4 , 1 ] ] ,
performAction : function anonymous ( yytext , yyleng , yylineno , yy , yystate , $$ , _$ ) {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
var $0 = $$ . length - 1 ;
switch ( yystate ) {
2016-05-03 17:37:10 +02:00
case 1 : return { type : 'GROUP' , expr : $$ [ $0 - 1 ] } ;
2016-03-16 12:49:35 +01:00
break ;
2016-05-03 17:37:10 +02:00
case 2 : this . $ = { type : 'TERNARY' , expr : $$ [ $0 - 4 ] , truthy : $$ [ $0 - 2 ] , falsey : $$ [ $0 ] } ;
2016-03-16 12:49:35 +01:00
break ;
case 3 : this . $ = { type : "OR" , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
case 4 : this . $ = { type : "AND" , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
2016-05-03 17:37:10 +02:00
case 5 : this . $ = { type : 'LT' , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
2016-03-16 12:49:35 +01:00
break ;
case 6 : this . $ = { type : 'LTE' , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
case 7 : this . $ = { type : 'GT' , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
case 8 : this . $ = { type : 'GTE' , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
case 9 : this . $ = { type : 'NEQ' , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
case 10 : this . $ = { type : 'EQ' , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
case 11 : this . $ = { type : 'MOD' , left : $$ [ $0 - 2 ] , right : $$ [ $0 ] } ;
break ;
2016-05-03 17:37:10 +02:00
case 12 : this . $ = { type : 'GROUP' , expr : $$ [ $0 - 1 ] } ;
2016-03-16 12:49:35 +01:00
break ;
2016-05-03 17:37:10 +02:00
case 13 : this . $ = { type : 'VAR' } ;
2016-03-16 12:49:35 +01:00
break ;
2016-05-03 17:37:10 +02:00
case 14 : this . $ = { type : 'NUM' , val : Number ( yytext ) } ;
2016-03-16 12:49:35 +01:00
break ;
}
} ,
table : [ { 3 : 1 , 4 : 2 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 1 : [ 3 ] } , { 5 : [ 1 , 6 ] , 6 : [ 1 , 7 ] , 8 : [ 1 , 8 ] , 9 : [ 1 , 9 ] , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 12 : [ 1 , 12 ] , 13 : [ 1 , 13 ] , 14 : [ 1 , 14 ] , 15 : [ 1 , 15 ] , 16 : [ 1 , 16 ] } , { 4 : 17 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 5 : [ 2 , 13 ] , 6 : [ 2 , 13 ] , 7 : [ 2 , 13 ] , 8 : [ 2 , 13 ] , 9 : [ 2 , 13 ] , 10 : [ 2 , 13 ] , 11 : [ 2 , 13 ] , 12 : [ 2 , 13 ] , 13 : [ 2 , 13 ] , 14 : [ 2 , 13 ] , 15 : [ 2 , 13 ] , 16 : [ 2 , 13 ] , 18 : [ 2 , 13 ] } , { 5 : [ 2 , 14 ] , 6 : [ 2 , 14 ] , 7 : [ 2 , 14 ] , 8 : [ 2 , 14 ] , 9 : [ 2 , 14 ] , 10 : [ 2 , 14 ] , 11 : [ 2 , 14 ] , 12 : [ 2 , 14 ] , 13 : [ 2 , 14 ] , 14 : [ 2 , 14 ] , 15 : [ 2 , 14 ] , 16 : [ 2 , 14 ] , 18 : [ 2 , 14 ] } , { 1 : [ 2 , 1 ] } , { 4 : 18 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 19 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 20 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 21 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 22 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 23 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 24 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 25 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 26 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 4 : 27 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 6 : [ 1 , 7 ] , 8 : [ 1 , 8 ] , 9 : [ 1 , 9 ] , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 12 : [ 1 , 12 ] , 13 : [ 1 , 13 ] , 14 : [ 1 , 14 ] , 15 : [ 1 , 15 ] , 16 : [ 1 , 16 ] , 18 : [ 1 , 28 ] } , { 6 : [ 1 , 7 ] , 7 : [ 1 , 29 ] , 8 : [ 1 , 8 ] , 9 : [ 1 , 9 ] , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 12 : [ 1 , 12 ] , 13 : [ 1 , 13 ] , 14 : [ 1 , 14 ] , 15 : [ 1 , 15 ] , 16 : [ 1 , 16 ] } , { 5 : [ 2 , 3 ] , 6 : [ 2 , 3 ] , 7 : [ 2 , 3 ] , 8 : [ 2 , 3 ] , 9 : [ 1 , 9 ] , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 12 : [ 1 , 12 ] , 13 : [ 1 , 13 ] , 14 : [ 1 , 14 ] , 15 : [ 1 , 15 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 3 ] } , { 5 : [ 2 , 4 ] , 6 : [ 2 , 4 ] , 7 : [ 2 , 4 ] , 8 : [ 2 , 4 ] , 9 : [ 2 , 4 ] , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 12 : [ 1 , 12 ] , 13 : [ 1 , 13 ] , 14 : [ 1 , 14 ] , 15 : [ 1 , 15 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 4 ] } , { 5 : [ 2 , 5 ] , 6 : [ 2 , 5 ] , 7 : [ 2 , 5 ] , 8 : [ 2 , 5 ] , 9 : [ 2 , 5 ] , 10 : [ 2 , 5 ] , 11 : [ 2 , 5 ] , 12 : [ 2 , 5 ] , 13 : [ 2 , 5 ] , 14 : [ 2 , 5 ] , 15 : [ 2 , 5 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 5 ] } , { 5 : [ 2 , 6 ] , 6 : [ 2 , 6 ] , 7 : [ 2 , 6 ] , 8 : [ 2 , 6 ] , 9 : [ 2 , 6 ] , 10 : [ 2 , 6 ] , 11 : [ 2 , 6 ] , 12 : [ 2 , 6 ] , 13 : [ 2 , 6 ] , 14 : [ 2 , 6 ] , 15 : [ 2 , 6 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 6 ] } , { 5 : [ 2 , 7 ] , 6 : [ 2 , 7 ] , 7 : [ 2 , 7 ] , 8 : [ 2 , 7 ] , 9 : [ 2 , 7 ] , 10 : [ 2 , 7 ] , 11 : [ 2 , 7 ] , 12 : [ 2 , 7 ] , 13 : [ 2 , 7 ] , 14 : [ 2 , 7 ] , 15 : [ 2 , 7 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 7 ] } , { 5 : [ 2 , 8 ] , 6 : [ 2 , 8 ] , 7 : [ 2 , 8 ] , 8 : [ 2 , 8 ] , 9 : [ 2 , 8 ] , 10 : [ 2 , 8 ] , 11 : [ 2 , 8 ] , 12 : [ 2 , 8 ] , 13 : [ 2 , 8 ] , 14 : [ 2 , 8 ] , 15 : [ 2 , 8 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 8 ] } , { 5 : [ 2 , 9 ] , 6 : [ 2 , 9 ] , 7 : [ 2 , 9 ] , 8 : [ 2 , 9 ] , 9 : [ 2 , 9 ] , 10 : [ 2 , 9 ] , 11 : [ 2 , 9 ] , 12 : [ 2 , 9 ] , 13 : [ 2 , 9 ] , 14 : [ 2 , 9 ] , 15 : [ 2 , 9 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 9 ] } , { 5 : [ 2 , 10 ] , 6 : [ 2 , 10 ] , 7 : [ 2 , 10 ] , 8 : [ 2 , 10 ] , 9 : [ 2 , 10 ] , 10 : [ 2 , 10 ] , 11 : [ 2 , 10 ] , 12 : [ 2 , 10 ] , 13 : [ 2 , 10 ] , 14 : [ 2 , 10 ] , 15 : [ 2 , 10 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 10 ] } , { 5 : [ 2 , 11 ] , 6 : [ 2 , 11 ] , 7 : [ 2 , 11 ] , 8 : [ 2 , 11 ] , 9 : [ 2 , 11 ] , 10 : [ 2 , 11 ] , 11 : [ 2 , 11 ] , 12 : [ 2 , 11 ] , 13 : [ 2 , 11 ] , 14 : [ 2 , 11 ] , 15 : [ 2 , 11 ] , 16 : [ 2 , 11 ] , 18 : [ 2 , 11 ] } , { 5 : [ 2 , 12 ] , 6 : [ 2 , 12 ] , 7 : [ 2 , 12 ] , 8 : [ 2 , 12 ] , 9 : [ 2 , 12 ] , 10 : [ 2 , 12 ] , 11 : [ 2 , 12 ] , 12 : [ 2 , 12 ] , 13 : [ 2 , 12 ] , 14 : [ 2 , 12 ] , 15 : [ 2 , 12 ] , 16 : [ 2 , 12 ] , 18 : [ 2 , 12 ] } , { 4 : 30 , 17 : [ 1 , 3 ] , 19 : [ 1 , 4 ] , 20 : [ 1 , 5 ] } , { 5 : [ 2 , 2 ] , 6 : [ 1 , 7 ] , 7 : [ 2 , 2 ] , 8 : [ 1 , 8 ] , 9 : [ 1 , 9 ] , 10 : [ 1 , 10 ] , 11 : [ 1 , 11 ] , 12 : [ 1 , 12 ] , 13 : [ 1 , 13 ] , 14 : [ 1 , 14 ] , 15 : [ 1 , 15 ] , 16 : [ 1 , 16 ] , 18 : [ 2 , 2 ] } ] ,
defaultActions : { 6 : [ 2 , 1 ] } ,
parseError : function parseError ( str , hash ) {
throw new Error ( str ) ;
} ,
parse : function parse ( input ) {
var self = this ,
stack = [ 0 ] ,
vstack = [ null ] , // semantic value stack
lstack = [ ] , // location stack
table = this . table ,
yytext = '' ,
yylineno = 0 ,
yyleng = 0 ,
recovering = 0 ,
TERROR = 2 ,
EOF = 1 ;
//this.reductionCount = this.shiftCount = 0;
this . lexer . setInput ( input ) ;
this . lexer . yy = this . yy ;
this . yy . lexer = this . lexer ;
if ( typeof this . lexer . yylloc == 'undefined' )
this . lexer . yylloc = { } ;
var yyloc = this . lexer . yylloc ;
lstack . push ( yyloc ) ;
if ( typeof this . yy . parseError === 'function' )
this . parseError = this . yy . parseError ;
function popStack ( n ) {
stack . length = stack . length - 2 * n ;
vstack . length = vstack . length - n ;
lstack . length = lstack . length - n ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
function lex ( ) {
var token ;
token = self . lexer . lex ( ) || 1 ; // $end = 1
// if token isn't its numeric value, convert
if ( typeof token !== 'number' ) {
token = self . symbols _ [ token ] || token ;
}
return token ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
var symbol , preErrorSymbol , state , action , a , r , yyval = { } , p , len , newState , expected ;
while ( true ) {
// retreive state number from top of stack
state = stack [ stack . length - 1 ] ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// use default actions if available
if ( this . defaultActions [ state ] ) {
action = this . defaultActions [ state ] ;
} else {
if ( symbol == null )
symbol = lex ( ) ;
// read action for current state and first input
action = table [ state ] && table [ state ] [ symbol ] ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// handle parse error
_handle _error :
if ( typeof action === 'undefined' || ! action . length || ! action [ 0 ] ) {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
if ( ! recovering ) {
// Report error
expected = [ ] ;
for ( p in table [ state ] ) if ( this . terminals _ [ p ] && p > 2 ) {
expected . push ( "'" + this . terminals _ [ p ] + "'" ) ;
}
var errStr = '' ;
if ( this . lexer . showPosition ) {
errStr = 'Parse error on line ' + ( yylineno + 1 ) + ":\n" + this . lexer . showPosition ( ) + "\nExpecting " + expected . join ( ', ' ) + ", got '" + this . terminals _ [ symbol ] + "'" ;
} else {
errStr = 'Parse error on line ' + ( yylineno + 1 ) + ": Unexpected " +
( symbol == 1 /*EOF*/ ? "end of input" :
( "'" + ( this . terminals _ [ symbol ] || symbol ) + "'" ) ) ;
}
this . parseError ( errStr ,
{ text : this . lexer . match , token : this . terminals _ [ symbol ] || symbol , line : this . lexer . yylineno , loc : yyloc , expected : expected } ) ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// just recovered from another error
if ( recovering == 3 ) {
if ( symbol == EOF ) {
throw new Error ( errStr || 'Parsing halted.' ) ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// discard current lookahead and grab another
yyleng = this . lexer . yyleng ;
yytext = this . lexer . yytext ;
yylineno = this . lexer . yylineno ;
yyloc = this . lexer . yylloc ;
symbol = lex ( ) ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// try to recover from error
while ( 1 ) {
// check for error recovery rule in this state
if ( ( TERROR . toString ( ) ) in table [ state ] ) {
break ;
}
if ( state == 0 ) {
throw new Error ( errStr || 'Parsing halted.' ) ;
}
popStack ( 1 ) ;
state = stack [ stack . length - 1 ] ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
preErrorSymbol = symbol ; // save the lookahead token
symbol = TERROR ; // insert generic error symbol as new lookahead
state = stack [ stack . length - 1 ] ;
action = table [ state ] && table [ state ] [ TERROR ] ;
recovering = 3 ; // allow 3 real symbols to be shifted before reporting a new error
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// this shouldn't happen, unless resolve defaults are off
if ( action [ 0 ] instanceof Array && action . length > 1 ) {
throw new Error ( 'Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol ) ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
switch ( action [ 0 ] ) {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
case 1 : // shift
//this.shiftCount++;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
stack . push ( symbol ) ;
vstack . push ( this . lexer . yytext ) ;
lstack . push ( this . lexer . yylloc ) ;
stack . push ( action [ 1 ] ) ; // push state
symbol = null ;
if ( ! preErrorSymbol ) { // normal execution/no error
yyleng = this . lexer . yyleng ;
yytext = this . lexer . yytext ;
yylineno = this . lexer . yylineno ;
yyloc = this . lexer . yylloc ;
if ( recovering > 0 )
recovering -- ;
} else { // error just occurred, resume old lookahead f/ before error
symbol = preErrorSymbol ;
preErrorSymbol = null ;
}
break ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
case 2 : // reduce
//this.reductionCount++;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
len = this . productions _ [ action [ 1 ] ] [ 1 ] ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// perform semantic action
yyval . $ = vstack [ vstack . length - len ] ; // default to $$ = $1
// default location, uses first token for firsts, last for lasts
yyval . _$ = {
first _line : lstack [ lstack . length - ( len || 1 ) ] . first _line ,
last _line : lstack [ lstack . length - 1 ] . last _line ,
first _column : lstack [ lstack . length - ( len || 1 ) ] . first _column ,
last _column : lstack [ lstack . length - 1 ] . last _column
} ;
r = this . performAction . call ( yyval , yytext , yyleng , yylineno , this . yy , action [ 1 ] , vstack , lstack ) ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
if ( typeof r !== 'undefined' ) {
return r ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// pop off stack
if ( len ) {
stack = stack . slice ( 0 , - 1 * len * 2 ) ;
vstack = vstack . slice ( 0 , - 1 * len ) ;
lstack = lstack . slice ( 0 , - 1 * len ) ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
stack . push ( this . productions _ [ action [ 1 ] ] [ 0 ] ) ; // push nonterminal (reduce)
vstack . push ( yyval . $ ) ;
lstack . push ( yyval . _$ ) ;
// goto new state = table[STATE][NONTERMINAL]
newState = table [ stack [ stack . length - 2 ] ] [ stack [ stack . length - 1 ] ] ;
stack . push ( newState ) ;
break ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
case 3 : // accept
return true ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
return true ;
} } ; /* Jison generated lexer */
var lexer = ( function ( ) {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
var lexer = ( { EOF : 1 ,
parseError : function parseError ( str , hash ) {
if ( this . yy . parseError ) {
this . yy . parseError ( str , hash ) ;
} else {
throw new Error ( str ) ;
}
} ,
setInput : function ( input ) {
this . _input = input ;
this . _more = this . _less = this . done = false ;
this . yylineno = this . yyleng = 0 ;
this . yytext = this . matched = this . match = '' ;
this . conditionStack = [ 'INITIAL' ] ;
this . yylloc = { first _line : 1 , first _column : 0 , last _line : 1 , last _column : 0 } ;
return this ;
} ,
input : function ( ) {
var ch = this . _input [ 0 ] ;
this . yytext += ch ;
this . yyleng ++ ;
this . match += ch ;
this . matched += ch ;
var lines = ch . match ( /\n/ ) ;
if ( lines ) this . yylineno ++ ;
this . _input = this . _input . slice ( 1 ) ;
return ch ;
} ,
unput : function ( ch ) {
this . _input = ch + this . _input ;
return this ;
} ,
more : function ( ) {
this . _more = true ;
return this ;
} ,
pastInput : function ( ) {
var past = this . matched . substr ( 0 , this . matched . length - this . match . length ) ;
return ( past . length > 20 ? '...' : '' ) + past . substr ( - 20 ) . replace ( /\n/g , "" ) ;
} ,
upcomingInput : function ( ) {
var next = this . match ;
if ( next . length < 20 ) {
next += this . _input . substr ( 0 , 20 - next . length ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return ( next . substr ( 0 , 20 ) + ( next . length > 20 ? '...' : '' ) ) . replace ( /\n/g , "" ) ;
} ,
showPosition : function ( ) {
var pre = this . pastInput ( ) ;
var c = new Array ( pre . length + 1 ) . join ( "-" ) ;
return pre + this . upcomingInput ( ) + "\n" + c + "^" ;
} ,
next : function ( ) {
if ( this . done ) {
return this . EOF ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
if ( ! this . _input ) this . done = true ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
var token ,
match ,
col ,
lines ;
if ( ! this . _more ) {
this . yytext = '' ;
this . match = '' ;
}
var rules = this . _currentRules ( ) ;
for ( var i = 0 ; i < rules . length ; i ++ ) {
match = this . _input . match ( this . rules [ rules [ i ] ] ) ;
if ( match ) {
lines = match [ 0 ] . match ( /\n.*/g ) ;
if ( lines ) this . yylineno += lines . length ;
this . yylloc = { first _line : this . yylloc . last _line ,
last _line : this . yylineno + 1 ,
first _column : this . yylloc . last _column ,
last _column : lines ? lines [ lines . length - 1 ] . length - 1 : this . yylloc . last _column + match [ 0 ] . length }
this . yytext += match [ 0 ] ;
this . match += match [ 0 ] ;
this . matches = match ;
this . yyleng = this . yytext . length ;
this . _more = false ;
this . _input = this . _input . slice ( match [ 0 ] . length ) ;
this . matched += match [ 0 ] ;
token = this . performAction . call ( this , this . yy , this , rules [ i ] , this . conditionStack [ this . conditionStack . length - 1 ] ) ;
if ( token ) return token ;
else return ;
}
}
if ( this . _input === "" ) {
return this . EOF ;
} else {
2016-05-03 17:37:10 +02:00
this . parseError ( 'Lexical error on line ' + ( this . yylineno + 1 ) + '. Unrecognized text.\n' + this . showPosition ( ) ,
2016-03-16 12:49:35 +01:00
{ text : "" , token : null , line : this . yylineno } ) ;
}
} ,
lex : function lex ( ) {
var r = this . next ( ) ;
if ( typeof r !== 'undefined' ) {
return r ;
} else {
return this . lex ( ) ;
}
} ,
begin : function begin ( condition ) {
this . conditionStack . push ( condition ) ;
} ,
popState : function popState ( ) {
return this . conditionStack . pop ( ) ;
} ,
_currentRules : function _currentRules ( ) {
return this . conditions [ this . conditionStack [ this . conditionStack . length - 1 ] ] . rules ;
} ,
topState : function ( ) {
return this . conditionStack [ this . conditionStack . length - 2 ] ;
} ,
pushState : function begin ( condition ) {
this . begin ( condition ) ;
} } ) ;
lexer . performAction = function anonymous ( yy , yy _ , $avoiding _name _collisions , YY _START ) {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
var YYSTATE = YY _START ;
switch ( $avoiding _name _collisions ) {
case 0 : /* skip whitespace */
break ;
case 1 : return 20
break ;
case 2 : return 19
break ;
case 3 : return 8
break ;
case 4 : return 9
break ;
case 5 : return 6
break ;
case 6 : return 7
break ;
case 7 : return 11
break ;
case 8 : return 13
break ;
case 9 : return 10
break ;
case 10 : return 12
break ;
case 11 : return 14
break ;
case 12 : return 15
break ;
case 13 : return 16
break ;
case 14 : return 17
break ;
case 15 : return 18
break ;
case 16 : return 5
break ;
case 17 : return 'INVALID'
break ;
}
} ;
lexer . rules = [ /^\s+/ , /^[0-9]+(\.[0-9]+)?\b/ , /^n\b/ , /^\|\|/ , /^&&/ , /^\?/ , /^:/ , /^<=/ , /^>=/ , /^</ , /^>/ , /^!=/ , /^==/ , /^%/ , /^\(/ , /^\)/ , /^$/ , /^./ ] ;
lexer . conditions = { "INITIAL" : { "rules" : [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 ] , "inclusive" : true } } ; return lexer ; } ) ( )
parser . lexer = lexer ;
return parser ;
} ) ( ) ;
// End parser
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// Handle node, amd, and global systems
if ( typeof exports !== 'undefined' ) {
if ( typeof module !== 'undefined' && module . exports ) {
exports = module . exports = Jed ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
exports . Jed = Jed ;
}
else {
if ( typeof define === 'function' && define . amd ) {
define ( 'jed' , [ ] , function ( ) {
return Jed ;
} ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
// Leak a global regardless of module system
root [ 'Jed' ] = Jed ;
}
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
} ) ( this ) ;
2016-03-07 18:54:07 +01:00
2016-05-03 17:37:10 +02:00
2016-09-16 14:35:02 +02:00
define ( 'text!ca' , [ ] , function ( ) { return '{\n "domain": "converse",\n "locale_data": {\n "converse": {\n "": {\n "domain": "converse",\n "plural_forms": "nplurals=2; plural=(n != 1);",\n "lang": "ca"\n },\n "Close this chat box": [\n null,\n "Tanca aquest quadre del xat"\n ],\n "Personal message": [\n null,\n "Missatge personal"\n ],\n "me": [\n null,\n "jo"\n ],\n "A very large message has been received.This might be due to an attack meant to degrade the chat performance.Output has been shortened.": [\n null,\n ""\n ],\n "is typing": [\n null,\n "està escrivint"\n ],\n "has stopped typing": [\n null,\n "ha deixat d\'escriure"\n ],\n "has gone away": [\n null,\n "ha marxat"\n ],\n "Show this menu": [\n null,\n "Mostra aquest menú"\n ],\n "Write in the third person": [\n null,\n "Escriu en tercera persona"\n ],\n "Remove messages": [\n null,\n "Elimina els missatges"\n ],\n "Are you sure you want to clear the messages from this chat box?": [\n null,\n "Segur que voleu esborrar els missatges d\'aquest quadre del xat?"\n ],\n "has gone offline": [\n null,\n "s\'ha desconnectat"\n ],\n "is busy": [\n null,\n "està ocupat"\n ],\n "Clear all messages": [\n null,\n "Esborra tots els missatges"\n ],\n "Hide the list of occupants": [\n null,\n "Amaga la llista d\'ocupants"\n ],\n "Insert a smiley": [\n null,\n "Insereix una cara somrient"\n ],\n "Start a call": [\n null,\n "Inicia una trucada"\n ],\n "Contacts": [\n null,\n "Contactes"\n ],\n "Connecting": [\n null,\n "S\'està establint la connexió"\n ],\n "XMPP Username:": [\n null,\n "Nom d\'usuari XMPP:"\n ],\n "Password:": [\n null,\n "Contrasenya:"\n ],\n "Click here to log in anonymously": [\n null,\n "Feu clic aquí per iniciar la sessió de manera anònima"\n ],\n "Log In": [\n null,\n "Inicia la sessió"\n ],\n "user@server": [\n null,\n "usuari@servidor"\n ],\n "password": [\n null,\n "contrasenya"\n ],\n "Sign in": [\n null,\n "Inicia la sessió"\n ],\n "I am %1$s": [\n null,\n "Estic %1$s"\n ],\n "Click here to write a custom status message": [\n null,\n "Feu clic aquí per escriure un missatge d\'estat personalitzat"\n ],\n "Click to change your chat status": [\n null,\n "Feu clic per canviar l\' estat del xat "\n ],\n " Custom status ": [\n null,\n " Estat personalitzat "\n ],\n " Save ": [\n null,\n " Desa "\n ],\n " online ": [\n null,\n " en línia "\n ],\n " busy ": [\n null,\n " ocupat "\n ],\n " away for long ": [\n null,\n " absent durant una estona "\n ],\n " away ": [\n null,\n " absent "\n ],\n " offline ": [\n null,\n " desconnectat "\n ],\n " Online ": [\n null,\n " En línia "\n ],\n " Busy ": [\n null,\n " Ocupat " \ n
2016-05-03 17:37:10 +02:00
2016-03-16 12:49:35 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global Backbone, define */
( function ( root , factory ) {
define ( "converse-chatview" , [ "converse-core" , "converse-api" ] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
var $ = converse _api . env . jQuery ,
utils = converse _api . env . utils ,
Strophe = converse _api . env . Strophe ,
$msg = converse _api . env . $msg ,
_ = converse _api . env . _ ,
_ _ = utils . _ _ . bind ( converse ) ,
moment = converse _api . env . moment ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
var KEY = {
ENTER : 13 ,
FORWARD _SLASH : 47
} ;
2016-03-07 18:54:07 +01:00
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-chatview' , {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
ChatBoxViews : {
onChatBoxAdded : function ( item ) {
var view = this . get ( item . get ( 'id' ) ) ;
2016-03-29 08:56:13 +02:00
if ( ! view ) {
2016-03-16 12:49:35 +01:00
view = new converse . ChatBoxView ( { model : item } ) ;
this . add ( item . get ( 'id' ) , view ) ;
2016-03-29 08:56:13 +02:00
return view ;
2016-03-16 12:49:35 +01:00
} else {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . onChatBoxAdded . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
}
2016-03-07 18:54:07 +01:00
}
}
2016-03-16 12:49:35 +01:00
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
this . updateSettings ( {
show _toolbar : true ,
} ) ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
converse . ChatBoxView = Backbone . View . extend ( {
length : 200 ,
tagName : 'div' ,
className : 'chatbox' ,
is _chatroom : false , // This is not a multi-user chatroom
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
events : {
'click .close-chatbox-button' : 'close' ,
'keypress textarea.chat-textarea' : 'keyPressed' ,
'click .toggle-smiley' : 'toggleEmoticonMenu' ,
'click .toggle-smiley ul li' : 'insertEmoticon' ,
'click .toggle-clear' : 'clearMessages' ,
2016-06-20 21:11:43 +02:00
'click .toggle-call' : 'toggleCall' ,
'click .new-msgs-indicator' : 'viewUnreadMessages'
2016-03-16 12:49:35 +01:00
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
initialize : function ( ) {
this . model . messages . on ( 'add' , this . onMessageAdded , this ) ;
this . model . on ( 'show' , this . show , this ) ;
this . model . on ( 'destroy' , this . hide , this ) ;
// TODO check for changed fullname as well
this . model . on ( 'change:chat_state' , this . sendChatState , this ) ;
this . model . on ( 'change:chat_status' , this . onChatStatusChanged , this ) ;
this . model . on ( 'change:image' , this . renderAvatar , this ) ;
this . model . on ( 'change:status' , this . onStatusChanged , this ) ;
this . model . on ( 'showHelpMessages' , this . showHelpMessages , this ) ;
this . model . on ( 'sendMessage' , this . sendMessage , this ) ;
2016-08-12 22:52:21 +02:00
this . render ( ) . fetchMessages ( ) . insertIntoDOM ( ) . afterShown ( ) ;
2016-06-20 21:11:43 +02:00
// XXX: adding the event below to the events map above doesn't work.
// The code that gets executed because of that looks like this:
// this.$el.on('scroll', '.chat-content', this.markScrolled.bind(this));
// Which for some reason doesn't work.
// So working around that fact here:
this . $el . find ( '.chat-content' ) . on ( 'scroll' , this . markScrolled . bind ( this ) ) ;
2016-03-16 12:49:35 +01:00
converse . emit ( 'chatBoxInitialized' , this ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
render : function ( ) {
this . $el . attr ( 'id' , this . model . get ( 'box_id' ) )
. html ( converse . templates . chatbox (
_ . extend ( this . model . toJSON ( ) , {
show _toolbar : converse . show _toolbar ,
show _textarea : true ,
title : this . model . get ( 'fullname' ) ,
2016-06-20 21:11:43 +02:00
unread _msgs : _ _ ( 'You have unread messages' ) ,
2016-03-16 12:49:35 +01:00
info _close : _ _ ( 'Close this chat box' ) ,
label _personal _message : _ _ ( 'Personal message' )
}
)
)
) ;
this . $content = this . $el . find ( '.chat-content' ) ;
this . renderToolbar ( ) . renderAvatar ( ) ;
converse . emit ( 'chatBoxOpened' , this ) ;
window . setTimeout ( utils . refreshWebkit , 50 ) ;
return this . showStatusMessage ( ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-04-01 15:33:14 +02:00
afterMessagesFetched : function ( ) {
// Provides a hook for plugins, such as converse-mam.
return ;
2016-03-16 12:49:35 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
fetchMessages : function ( ) {
this . model . messages . fetch ( {
'add' : true ,
2016-04-01 15:33:14 +02:00
'success' : this . afterMessagesFetched . bind ( this )
2016-03-16 12:49:35 +01:00
} ) ;
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-07-26 08:00:30 +02:00
insertIntoDOM : function ( ) {
2016-03-16 12:49:35 +01:00
/ * T h i s m e t h o d g e t s o v e r r i d d e n i n s r c / c o n v e r s e - c o n t r o l b o x . j s i f
2016-03-29 08:56:13 +02:00
* the controlbox plugin is active .
* /
2016-03-16 12:49:35 +01:00
$ ( '#conversejs' ) . prepend ( this . $el ) ;
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
clearStatusNotification : function ( ) {
this . $content . find ( 'div.chat-event' ) . remove ( ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-09-16 14:35:02 +02:00
showStatusNotification : function ( message , keep _old , permanent ) {
2016-03-16 12:49:35 +01:00
if ( ! keep _old ) {
this . clearStatusNotification ( ) ;
}
2016-09-16 14:35:02 +02:00
var $el = $ ( '<div class="chat-info"></div>' ) . text ( message ) ;
if ( ! permanent ) {
$el . addClass ( 'chat-event' ) ;
}
this . $content . append ( $el ) ;
2016-06-20 21:11:43 +02:00
this . scrollDown ( ) ;
2016-03-16 12:49:35 +01:00
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
addSpinner : function ( ) {
if ( ! this . $content . first ( ) . hasClass ( 'spinner' ) ) {
this . $content . prepend ( '<span class="spinner"/>' ) ;
}
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
clearSpinner : function ( ) {
if ( this . $content . children ( ':first' ) . is ( 'span.spinner' ) ) {
this . $content . children ( ':first' ) . remove ( ) ;
}
} ,
2016-03-07 18:54:07 +01:00
2016-06-20 21:11:43 +02:00
insertDayIndicator : function ( date , prepend ) {
/ * A p p e n d s ( o r p r e p e n d s i f " p r e p e n d " i s t r u t h y ) a n i n d i c a t o r
* into the chat area , showing the day as given by the
* passed in date .
2016-03-29 08:56:13 +02:00
*
* Parameters :
* ( String ) date - An ISO8601 date string .
* /
2016-03-16 12:49:35 +01:00
var day _date = moment ( date ) . startOf ( 'day' ) ;
2016-06-20 21:11:43 +02:00
var insert = prepend ? this . $content . prepend : this . $content . append ;
insert . call ( this . $content , converse . templates . new _day ( {
2016-03-16 12:49:35 +01:00
isodate : day _date . format ( ) ,
datestring : day _date . format ( "dddd MMM Do YYYY" )
} ) ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-06-20 21:11:43 +02:00
insertMessage : function ( attrs , prepend ) {
/ * H e l p e r m e t h o d w h i c h a p p e n d s a m e s s a g e ( o r p r e p e n d s i f t h e
* 2 nd parameter is set to true ) to the end of the chat box ' s
* content area .
2016-03-29 08:56:13 +02:00
*
* Parameters :
* ( Object ) attrs : An object containing the message attributes .
* /
2016-06-20 21:11:43 +02:00
var insert = prepend ? this . $content . prepend : this . $content . append ;
2016-03-16 12:49:35 +01:00
_ . compose (
2016-06-20 21:11:43 +02:00
this . scrollDownMessageHeight . bind ( this ) ,
function ( $el ) {
insert . call ( this . $content , $el ) ;
return $el ;
} . bind ( this )
2016-03-16 12:49:35 +01:00
) ( this . renderMessage ( attrs ) ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
showMessage : function ( attrs ) {
/ * I n s e r t s a c h a t m e s s a g e i n t o t h e c o n t e n t a r e a o f t h e c h a t b o x .
2016-03-29 08:56:13 +02:00
* Will also insert a new day indicator if the message is on a
* different day .
*
* The message to show may either be newer than the newest
* message , or older than the oldest message .
*
* Parameters :
* ( Object ) attrs : An object containing the message attributes .
* /
2016-06-20 21:11:43 +02:00
var msg _dates , idx ,
$first _msg = this . $content . children ( '.chat-message:first' ) ,
2016-03-16 12:49:35 +01:00
first _msg _date = $first _msg . data ( 'isodate' ) ,
2016-06-20 21:11:43 +02:00
current _msg _date = moment ( attrs . time ) || moment ,
last _msg _date = this . $content . children ( '.chat-message:last' ) . data ( 'isodate' ) ;
2016-03-16 12:49:35 +01:00
if ( ! first _msg _date ) {
2016-06-20 21:11:43 +02:00
// This is the first received message, so we insert a
// date indicator before it.
this . insertDayIndicator ( current _msg _date ) ;
this . insertMessage ( attrs ) ;
2016-03-16 12:49:35 +01:00
return ;
}
2016-06-20 21:11:43 +02:00
if ( current _msg _date . isAfter ( last _msg _date ) || current _msg _date . isSame ( last _msg _date ) ) {
2016-03-16 12:49:35 +01:00
// The new message is after the last message
if ( current _msg _date . isAfter ( last _msg _date , 'day' ) ) {
// Append a new day indicator
2016-06-20 21:11:43 +02:00
this . insertDayIndicator ( current _msg _date ) ;
2016-03-16 12:49:35 +01:00
}
2016-06-20 21:11:43 +02:00
this . insertMessage ( attrs ) ;
2016-03-16 12:49:35 +01:00
return ;
}
2016-06-20 21:11:43 +02:00
if ( current _msg _date . isBefore ( first _msg _date ) || current _msg _date . isSame ( first _msg _date ) ) {
// The message is before the first, but on the same day.
// We need to prepend the message immediately before the
// first message (so that it'll still be after the day indicator).
this . insertMessage ( attrs , 'prepend' ) ;
2016-03-16 12:49:35 +01:00
if ( current _msg _date . isBefore ( first _msg _date , 'day' ) ) {
2016-06-20 21:11:43 +02:00
// This message is also on a different day, so we prepend a day indicator.
this . insertDayIndicator ( current _msg _date , 'prepend' ) ;
2016-03-16 12:49:35 +01:00
}
2016-06-20 21:11:43 +02:00
return ;
2016-03-16 12:49:35 +01:00
}
2016-06-20 21:11:43 +02:00
// Find the correct place to position the message
current _msg _date = current _msg _date . format ( ) ;
msg _dates = _ . map ( this . $content . children ( '.chat-message' ) , function ( el ) {
return $ ( el ) . data ( 'isodate' ) ;
} ) ;
msg _dates . push ( current _msg _date ) ;
msg _dates . sort ( ) ;
idx = msg _dates . indexOf ( current _msg _date ) - 1 ;
_ . compose (
this . scrollDownMessageHeight . bind ( this ) ,
function ( $el ) {
$el . insertAfter ( this . $content . find ( '.chat-message[data-isodate="' + msg _dates [ idx ] + '"]' ) ) ;
return $el ;
} . bind ( this )
) ( this . renderMessage ( attrs ) ) ;
2016-03-16 12:49:35 +01:00
} ,
2016-03-07 18:54:07 +01:00
2016-07-26 08:00:30 +02:00
getExtraMessageTemplateAttributes : function ( attrs ) {
// Provides a hook for sending more attributes to the
// message template.
return { } ;
} ,
2016-03-16 12:49:35 +01:00
renderMessage : function ( attrs ) {
/ * R e n d e r s a c h a t m e s s a g e b a s e d o n t h e p a s s e d i n a t t r i b u t e s .
2016-03-29 08:56:13 +02:00
*
* Parameters :
* ( Object ) attrs : An object containing the message attributes .
*
* Returns :
* The DOM element representing the message .
* /
2016-03-16 12:49:35 +01:00
var msg _time = moment ( attrs . time ) || moment ,
text = attrs . message ,
match = text . match ( /^\/(.*?)(?: (.*))?$/ ) ,
fullname = this . model . get ( 'fullname' ) || attrs . fullname ,
extra _classes = attrs . delayed && 'delayed' || '' ,
template , username ;
if ( ( match ) && ( match [ 1 ] === 'me' ) ) {
text = text . replace ( /^\/me/ , '' ) ;
template = converse . templates . action ;
username = fullname ;
} else {
template = converse . templates . message ;
username = attrs . sender === 'me' && _ _ ( 'me' ) || fullname ;
}
this . $content . find ( 'div.chat-event' ) . remove ( ) ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
// FIXME: leaky abstraction from MUC
if ( this . is _chatroom && attrs . sender === 'them' && ( new RegExp ( "\\b" + this . model . get ( 'nick' ) + "\\b" ) ) . test ( text ) ) {
// Add special class to mark groupchat messages in which we
// are mentioned.
extra _classes += ' mentioned' ;
}
2016-09-16 14:35:02 +02:00
if ( text . length > 8000 ) {
text = text . substring ( 0 , 10 ) + '...' ;
this . showStatusNotification (
_ _ ( "A very large message has been received." +
"This might be due to an attack meant to degrade the chat performance." +
"Output has been shortened." ) ,
true , true ) ;
}
2016-07-26 08:00:30 +02:00
return $ ( template (
_ . extend ( this . getExtraMessageTemplateAttributes ( attrs ) , {
'msgid' : attrs . msgid ,
'sender' : attrs . sender ,
'time' : msg _time . format ( 'hh:mm' ) ,
'isodate' : msg _time . format ( ) ,
'username' : username ,
'message' : '' ,
'extra_classes' : extra _classes
} )
) ) . children ( '.chat-msg-content' ) . first ( ) . text ( text )
2016-03-16 12:49:35 +01:00
. addHyperlinks ( )
. addEmoticons ( converse . visible _toolbar _buttons . emoticons ) . parent ( ) ;
} ,
showHelpMessages : function ( msgs , type , spinner ) {
var i , msgs _length = msgs . length ;
for ( i = 0 ; i < msgs _length ; i ++ ) {
this . $content . append ( $ ( '<div class="chat-' + ( type || 'info' ) + '">' + msgs [ i ] + '</div>' ) ) ;
}
if ( spinner === true ) {
this . $content . append ( '<span class="spinner"/>' ) ;
} else if ( spinner === false ) {
this . $content . find ( 'span.spinner' ) . remove ( ) ;
}
return this . scrollDown ( ) ;
} ,
handleChatStateMessage : function ( message ) {
if ( message . get ( 'chat_state' ) === converse . COMPOSING ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'is typing' ) ) ;
2016-09-16 14:35:02 +02:00
this . clear _status _timeout = window . setTimeout ( this . clearStatusNotification . bind ( this ) , 30000 ) ;
2016-03-16 12:49:35 +01:00
} else if ( message . get ( 'chat_state' ) === converse . PAUSED ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'has stopped typing' ) ) ;
} else if ( _ . contains ( [ converse . INACTIVE , converse . ACTIVE ] , message . get ( 'chat_state' ) ) ) {
this . $content . find ( 'div.chat-event' ) . remove ( ) ;
} else if ( message . get ( 'chat_state' ) === converse . GONE ) {
this . showStatusNotification ( message . get ( 'fullname' ) + ' ' + _ _ ( 'has gone away' ) ) ;
}
} ,
2016-03-29 08:56:13 +02:00
shouldShowOnTextMessage : function ( ) {
return ! this . $el . is ( ':visible' ) ;
} ,
2016-06-20 21:11:43 +02:00
updateNewMessageIndicators : function ( message ) {
/ * W e h a v e t w o i n d i c a t o r s o f n e w m e s s a g e s . T h e u n r e a d m e s s a g e s
* counter , which shows the number of unread messages in
* the document . title , and the "new messages" indicator in
* a chat area , if it ' s scrolled up so that new messages
* aren ' t visible .
*
* In both cases we ignore MAM messages .
* /
if ( ! message . get ( 'archive_id' ) ) {
if ( this . model . get ( 'scrolled' , true ) ) {
this . $el . find ( '.new-msgs-indicator' ) . removeClass ( 'hidden' ) ;
}
if ( converse . windowState === 'hidden' || this . model . get ( 'scrolled' , true ) ) {
converse . incrementMsgCounter ( ) ;
}
}
} ,
2016-03-16 12:49:35 +01:00
handleTextMessage : function ( message ) {
this . showMessage ( _ . clone ( message . attributes ) ) ;
2016-06-20 21:11:43 +02:00
if ( message . get ( 'sender' ) !== 'me' ) {
this . updateNewMessageIndicators ( message ) ;
} else {
// We remove the "scrolled" flag so that the chat area
// gets scrolled down. We always want to scroll down
// when the user writes a message as opposed to when a
// message is received.
this . model . set ( 'scrolled' , false ) ;
2016-03-16 12:49:35 +01:00
}
2016-03-29 08:56:13 +02:00
if ( this . shouldShowOnTextMessage ( ) ) {
2016-03-16 12:49:35 +01:00
this . show ( ) ;
2016-06-20 21:11:43 +02:00
} else {
this . scrollDown ( ) ;
2016-03-16 12:49:35 +01:00
}
} ,
2016-02-26 18:28:18 +01:00
2016-07-26 08:00:30 +02:00
handleErrorMessage : function ( message ) {
var $message = $ ( '[data-msgid=' + message . get ( 'msgid' ) + ']' ) ;
if ( $message . length ) {
$message . after ( $ ( '<div class="chat-info chat-error"></div>' ) . text ( message . get ( 'message' ) ) ) ;
this . scrollDown ( ) ;
}
} ,
2016-03-16 12:49:35 +01:00
onMessageAdded : function ( message ) {
/ * H a n d l e r t h a t g e t s c a l l e d w h e n a n e w m e s s a g e o b j e c t i s c r e a t e d .
2016-03-29 08:56:13 +02:00
*
* Parameters :
* ( Object ) message - The message Backbone object that was added .
* /
2016-03-16 12:49:35 +01:00
if ( typeof this . clear _status _timeout !== 'undefined' ) {
window . clearTimeout ( this . clear _status _timeout ) ;
delete this . clear _status _timeout ;
}
2016-07-26 08:00:30 +02:00
if ( message . get ( 'type' ) === 'error' ) {
this . handleErrorMessage ( message ) ;
} else if ( ! message . get ( 'message' ) ) {
2016-03-16 12:49:35 +01:00
this . handleChatStateMessage ( message ) ;
} else {
this . handleTextMessage ( message ) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
createMessageStanza : function ( message ) {
return $msg ( {
from : converse . connection . jid ,
to : this . model . get ( 'jid' ) ,
type : 'chat' ,
id : message . get ( 'msgid' )
} ) . c ( 'body' ) . t ( message . get ( 'message' ) ) . up ( )
. c ( converse . ACTIVE , { 'xmlns' : Strophe . NS . CHATSTATES } ) . up ( ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
sendMessage : function ( message ) {
/ * R e s p o n s i b l e f o r s e n d i n g o f f a t e x t m e s s a g e .
2016-03-29 08:56:13 +02:00
*
* Parameters :
* ( Message ) message - The chat message
* /
2016-03-16 12:49:35 +01:00
// TODO: We might want to send to specfic resources.
// Especially in the OTR case.
var messageStanza = this . createMessageStanza ( message ) ;
converse . connection . send ( messageStanza ) ;
if ( converse . forward _messages ) {
// Forward the message, so that other connected resources are also aware of it.
converse . connection . send (
$msg ( { to : converse . bare _jid , type : 'chat' , id : message . get ( 'msgid' ) } )
. c ( 'forwarded' , { xmlns : 'urn:xmpp:forward:0' } )
. c ( 'delay' , { xmns : 'urn:xmpp:delay' , stamp : ( new Date ( ) ) . getTime ( ) } ) . up ( )
. cnode ( messageStanza . tree ( ) )
) ;
}
} ,
onMessageSubmitted : function ( text ) {
/ * T h i s m e t h o d g e t s c a l l e d o n c e t h e u s e r h a s t y p e d a m e s s a g e
2016-03-29 08:56:13 +02:00
* and then pressed enter in a chat box .
*
* Parameters :
* ( string ) text - The chat message text .
* /
2016-03-16 12:49:35 +01:00
if ( ! converse . connection . authenticated ) {
return this . showHelpMessages (
[ 'Sorry, the connection has been lost, ' +
'and your message could not be sent' ] ,
'error'
) ;
}
var match = text . replace ( /^\s*/ , "" ) . match ( /^\/(.*)\s*$/ ) , msgs ;
if ( match ) {
if ( match [ 1 ] === "clear" ) {
return this . clearMessages ( ) ;
}
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' ) + ''
] ;
this . showHelpMessages ( msgs ) ;
return ;
}
}
var fullname = converse . xmppstatus . get ( 'fullname' ) ;
fullname = _ . isEmpty ( fullname ) ? converse . bare _jid : fullname ;
var message = this . model . messages . create ( {
fullname : fullname ,
sender : 'me' ,
time : moment ( ) . format ( ) ,
message : text
} ) ;
this . sendMessage ( message ) ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
sendChatState : function ( ) {
/ * S e n d s a m e s s a g e w i t h t h e s t a t u s o f t h e u s e r i n t h i s c h a t s e s s i o n
2016-03-29 08:56:13 +02:00
* as taken from the 'chat_state' attribute of the chat box .
* See XEP - 0085 Chat State Notifications .
* /
2016-03-16 12:49:35 +01:00
converse . connection . send (
$msg ( { 'to' : this . model . get ( 'jid' ) , 'type' : 'chat' } )
2016-06-20 21:11:43 +02:00
. c ( this . model . get ( 'chat_state' ) , { 'xmlns' : Strophe . NS . CHATSTATES } ) . up ( )
. c ( 'no-store' , { 'xmlns' : Strophe . NS . HINTS } ) . up ( )
. c ( 'no-permanent-store' , { 'xmlns' : Strophe . NS . HINTS } )
2016-03-16 12:49:35 +01:00
) ;
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
setChatState : function ( state , no _save ) {
/ * M u t a t o r f o r s e t t i n g t h e c h a t s t a t e o f t h i s c h a t s e s s i o n .
2016-03-29 08:56:13 +02:00
* Handles clearing of any chat state notification timeouts and
* setting new ones if necessary .
* Timeouts are set when the state being set is COMPOSING or PAUSED .
* After the timeout , COMPOSING will become PAUSED and PAUSED will become INACTIVE .
* See XEP - 0085 Chat State Notifications .
*
* Parameters :
* ( string ) state - The chat state ( consts ACTIVE , COMPOSING , PAUSED , INACTIVE , GONE )
* ( Boolean ) no _save - Just do the cleanup or setup but don ' t actually save the state .
* /
2016-03-16 12:49:35 +01:00
if ( typeof this . chat _state _timeout !== 'undefined' ) {
window . clearTimeout ( this . chat _state _timeout ) ;
delete this . chat _state _timeout ;
}
if ( state === converse . COMPOSING ) {
this . chat _state _timeout = window . setTimeout (
this . setChatState . bind ( this ) , converse . TIMEOUTS . PAUSED , converse . PAUSED ) ;
} else if ( state === converse . PAUSED ) {
this . chat _state _timeout = window . setTimeout (
this . setChatState . bind ( this ) , converse . TIMEOUTS . INACTIVE , converse . INACTIVE ) ;
}
if ( ! no _save && this . model . get ( 'chat_state' ) !== state ) {
this . model . set ( 'chat_state' , state ) ;
}
return this ;
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
keyPressed : function ( ev ) {
/ * E v e n t h a n d l e r f o r w h e n a k e y i s p r e s s e d i n a c h a t b o x t e x t a r e a .
2016-03-29 08:56:13 +02:00
* /
2016-03-16 12:49:35 +01:00
var $textarea = $ ( ev . target ) , message ;
if ( ev . keyCode === KEY . ENTER ) {
ev . preventDefault ( ) ;
message = $textarea . val ( ) ;
$textarea . val ( '' ) . focus ( ) ;
if ( message !== '' ) {
2016-08-12 22:52:21 +02:00
this . onMessageSubmitted ( message ) ;
2016-03-16 12:49:35 +01:00
converse . emit ( 'messageSend' , message ) ;
}
this . setChatState ( converse . ACTIVE ) ;
2016-08-12 22:52:21 +02:00
} else {
2016-03-16 12:49:35 +01:00
// Set chat state to composing if keyCode is not a forward-slash
// (which would imply an internal command and not a message).
this . setChatState ( converse . COMPOSING , ev . keyCode === KEY . FORWARD _SLASH ) ;
}
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
clearMessages : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var result = confirm ( _ _ ( "Are you sure you want to clear the messages from this chat box?" ) ) ;
if ( result === true ) {
this . $content . empty ( ) ;
this . model . messages . reset ( ) ;
this . model . messages . browserStorage . _clear ( ) ;
}
return this ;
} ,
2016-03-07 18:54:07 +01:00
2016-07-28 18:06:31 +02:00
insertIntoTextArea : function ( value ) {
var $textbox = this . $el . find ( 'textarea.chat-textarea' ) ;
var existing = $textbox . val ( ) ;
if ( existing && ( existing [ existing . length - 1 ] !== ' ' ) ) {
existing = existing + ' ' ;
}
$textbox . focus ( ) . val ( existing + value + ' ' ) ;
} ,
2016-03-16 12:49:35 +01:00
insertEmoticon : function ( ev ) {
ev . stopPropagation ( ) ;
this . $el . find ( '.toggle-smiley ul' ) . slideToggle ( 200 ) ;
var $target = $ ( ev . target ) ;
$target = $target . is ( 'a' ) ? $target : $target . children ( 'a' ) ;
2016-07-28 18:06:31 +02:00
this . insertIntoTextArea ( $target . data ( 'emoticon' ) ) ;
2016-03-16 12:49:35 +01:00
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
toggleEmoticonMenu : function ( ev ) {
ev . stopPropagation ( ) ;
this . $el . find ( '.toggle-smiley ul' ) . slideToggle ( 200 ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
toggleCall : function ( ev ) {
ev . stopPropagation ( ) ;
converse . emit ( 'callButtonClicked' , {
connection : converse . connection ,
model : this . model
} ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
onChatStatusChanged : function ( item ) {
var chat _status = item . get ( 'chat_status' ) ,
fullname = item . get ( 'fullname' ) ;
fullname = _ . isEmpty ( fullname ) ? item . get ( 'jid' ) : fullname ;
if ( this . $el . is ( ':visible' ) ) {
if ( chat _status === 'offline' ) {
this . showStatusNotification ( fullname + ' ' + _ _ ( 'has gone offline' ) ) ;
} else if ( chat _status === 'away' ) {
this . showStatusNotification ( fullname + ' ' + _ _ ( 'has gone away' ) ) ;
} else if ( ( chat _status === 'dnd' ) ) {
this . showStatusNotification ( fullname + ' ' + _ _ ( 'is busy' ) ) ;
} else if ( chat _status === 'online' ) {
this . $el . find ( 'div.chat-event' ) . remove ( ) ;
}
}
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
onStatusChanged : function ( item ) {
this . showStatusMessage ( ) ;
converse . emit ( 'contactStatusMessageChanged' , {
'contact' : item . attributes ,
'message' : item . get ( 'status' )
} ) ;
} ,
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
showStatusMessage : function ( msg ) {
msg = msg || this . model . get ( 'status' ) ;
if ( typeof msg === "string" ) {
this . $el . find ( 'p.user-custom-message' ) . text ( msg ) . attr ( 'title' , msg ) ;
}
return this ;
} ,
close : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
if ( converse . connection . connected ) {
2016-03-29 08:56:13 +02:00
// Immediately sending the chat state, because the
// model is going to be destroyed afterwards.
this . model . set ( 'chat_state' , converse . INACTIVE ) ;
this . sendChatState ( ) ;
2016-03-16 12:49:35 +01:00
this . model . destroy ( ) ;
}
2016-03-29 08:56:13 +02:00
this . remove ( ) ;
2016-03-16 12:49:35 +01:00
converse . emit ( 'chatBoxClosed' , this ) ;
return this ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
renderToolbar : function ( options ) {
if ( ! converse . show _toolbar ) {
return ;
}
options = _ . extend ( options || { } , {
label _clear : _ _ ( 'Clear all messages' ) ,
label _hide _occupants : _ _ ( 'Hide the list of occupants' ) ,
label _insert _smiley : _ _ ( 'Insert a smiley' ) ,
label _start _call : _ _ ( 'Start a call' ) ,
show _call _button : converse . visible _toolbar _buttons . call ,
show _clear _button : converse . visible _toolbar _buttons . clear ,
show _emoticons : converse . visible _toolbar _buttons . emoticons ,
// FIXME Leaky abstraction MUC
show _occupants _toggle : this . is _chatroom && converse . visible _toolbar _buttons . toggle _occupants
} ) ;
this . $el . find ( '.chat-toolbar' ) . html ( converse . templates . toolbar ( _ . extend ( this . model . toJSON ( ) , options || { } ) ) ) ;
return this ;
} ,
renderAvatar : function ( ) {
if ( ! this . model . get ( 'image' ) ) {
return ;
}
var img _src = 'data:' + this . model . get ( 'image_type' ) + ';base64,' + this . model . get ( 'image' ) ,
canvas = $ ( '<canvas height="32px" width="32px" class="avatar"></canvas>' ) . get ( 0 ) ;
if ( ! ( canvas . getContext && canvas . getContext ( '2d' ) ) ) {
return this ;
}
var ctx = canvas . getContext ( '2d' ) ;
var img = new Image ( ) ; // Create new Image object
img . onload = function ( ) {
var ratio = img . width / img . height ;
if ( ratio < 1 ) {
ctx . drawImage ( img , 0 , 0 , 32 , 32 * ( 1 / ratio ) ) ;
} else {
ctx . drawImage ( img , 0 , 0 , 32 , 32 * ratio ) ;
}
} ;
img . src = img _src ;
this . $el . find ( '.chat-title' ) . before ( canvas ) ;
return this ;
} ,
focus : function ( ) {
this . $el . find ( '.chat-textarea' ) . focus ( ) ;
converse . emit ( 'chatBoxFocused' , this ) ;
return this ;
} ,
hide : function ( ) {
2016-06-20 21:11:43 +02:00
this . $el . hide ( ) ;
utils . refreshWebkit ( ) ;
2016-03-16 12:49:35 +01:00
return this ;
} ,
2016-03-29 08:56:13 +02:00
afterShown : function ( ) {
if ( converse . connection . connected ) {
// Without a connection, we haven't yet initialized
// localstorage
this . model . save ( ) ;
}
this . setChatState ( converse . ACTIVE ) ;
this . scrollDown ( ) ;
if ( focus ) {
this . focus ( ) ;
}
} ,
2016-04-01 15:33:14 +02:00
_show : function ( focus ) {
/* Inner show method that gets debounced */
if ( this . $el . is ( ':visible' ) && this . $el . css ( 'opacity' ) === "1" ) {
if ( focus ) { this . focus ( ) ; }
return ;
}
this . $el . fadeIn ( this . afterShown . bind ( this ) ) ;
} ,
2016-03-16 12:49:35 +01:00
show : function ( focus ) {
if ( typeof this . debouncedShow === 'undefined' ) {
/ * W e w r a p t h e m e t h o d i n a d e b o u n c e r a n d s e t i t o n t h e
2016-03-29 08:56:13 +02:00
* instance , so that we have it debounced per instance .
* Debouncing it on the class - level is too broad .
* /
2016-04-01 15:33:14 +02:00
this . debouncedShow = _ . debounce ( this . _show , 250 , true ) ;
2016-03-16 12:49:35 +01:00
}
this . debouncedShow . apply ( this , arguments ) ;
return this ;
} ,
2016-06-20 21:11:43 +02:00
markScrolled : _ . debounce ( function ( ev ) {
/ * C a l l e d w h e n t h e c h a t c o n t e n t i s s c r o l l e d u p o r d o w n .
* We want to record when the user has scrolled away from
* the bottom , so that we don ' t automatically scroll away
* from what the user is reading when new messages are
* received .
* /
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var is _at _bottom = this . $content . scrollTop ( ) + this . $content . innerHeight ( ) >= this . $content [ 0 ] . scrollHeight - 10 ;
if ( is _at _bottom ) {
this . model . set ( 'scrolled' , false ) ;
this . $el . find ( '.new-msgs-indicator' ) . addClass ( 'hidden' ) ;
} else {
// We're not at the bottom of the chat area, so we mark
// that the box is in a scrolled-up state.
this . model . set ( 'scrolled' , true ) ;
}
} , 150 ) ,
viewUnreadMessages : function ( ) {
this . model . set ( 'scrolled' , false ) ;
this . scrollDown ( ) ;
} ,
2016-03-16 12:49:35 +01:00
scrollDownMessageHeight : function ( $message ) {
2016-06-20 21:11:43 +02:00
if ( this . $content . is ( ':visible' ) && ! this . model . get ( 'scrolled' ) ) {
2016-03-16 12:49:35 +01:00
this . $content . scrollTop ( this . $content . scrollTop ( ) + $message [ 0 ] . scrollHeight ) ;
}
return this ;
} ,
scrollDown : function ( ) {
2016-06-20 21:11:43 +02:00
if ( this . $content . is ( ':visible' ) && ! this . model . get ( 'scrolled' ) ) {
2016-03-16 12:49:35 +01:00
this . $content . scrollTop ( this . $content [ 0 ] . scrollHeight ) ;
2016-06-20 21:11:43 +02:00
this . $el . find ( '.new-msgs-indicator' ) . addClass ( 'hidden' ) ;
2016-03-16 12:49:35 +01:00
}
return this ;
}
} ) ;
}
} ) ;
} ) ) ;
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-04-01 15:33:14 +02:00
/*global Backbone, define */
2016-02-26 18:28:18 +01:00
2016-02-28 10:00:22 +01:00
( function ( root , factory ) {
2016-04-01 15:33:14 +02:00
define ( "converse-rosterview" , [ "converse-core" , "converse-api" ] , factory ) ;
2016-03-07 18:54:07 +01:00
} ( this , function ( converse , converse _api ) {
"use strict" ;
var $ = converse _api . env . jQuery ,
2016-04-01 15:33:14 +02:00
utils = converse _api . env . utils ,
2016-03-16 12:49:35 +01:00
Strophe = converse _api . env . Strophe ,
$iq = converse _api . env . $iq ,
b64 _sha1 = converse _api . env . b64 _sha1 ,
_ = converse _api . env . _ ,
_ _ = utils . _ _ . bind ( converse ) ;
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
converse _api . plugins . add ( 'rosterview' , {
2016-03-07 18:54:07 +01:00
2016-03-16 12:49:35 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
afterReconnected : function ( ) {
this . rosterview . registerRosterXHandler ( ) ;
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . afterReconnected . apply ( this , arguments ) ;
2016-07-26 08:00:30 +02:00
} ,
2016-09-21 17:24:32 +02:00
initRoster : function ( ) {
/ * C r e a t e a n i n s t a n c e o f R o s t e r V i e w o n c e t h e R o s t e r G r o u p s
* collection has been created ( in converse - core . js )
* /
this . _ _super _ _ . initRoster . apply ( this , arguments ) ;
converse . rosterview = new converse . RosterView ( {
'model' : converse . rostergroups
} ) ;
converse . rosterview . render ( ) ;
} ,
2016-07-26 08:00:30 +02:00
RosterGroups : {
comparator : function ( ) {
// RosterGroupsComparator only gets set later (once i18n is
// set up), so we need to wrap it in this nameless function.
return converse . RosterGroupsComparator . apply ( this , arguments ) ;
}
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
2016-03-07 18:54:07 +01:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
2016-03-16 12:49:35 +01:00
this . updateSettings ( {
2016-03-19 14:33:31 +01:00
allow _chat _pending _contacts : false ,
allow _contact _removal : true ,
2016-03-16 12:49:35 +01:00
show _toolbar : true ,
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01: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' )
} ;
var LABEL _CONTACTS = _ _ ( 'Contacts' ) ;
var LABEL _GROUPS = _ _ ( 'Groups' ) ;
var HEADER _CURRENT _CONTACTS = _ _ ( 'My contacts' ) ;
var HEADER _PENDING _CONTACTS = _ _ ( 'Pending contacts' ) ;
var HEADER _REQUESTING _CONTACTS = _ _ ( 'Contact requests' ) ;
var HEADER _UNGROUPED = _ _ ( 'Ungrouped' ) ;
var HEADER _WEIGHTS = { } ;
2016-07-26 08:00:30 +02:00
HEADER _WEIGHTS [ HEADER _REQUESTING _CONTACTS ] = 0 ;
HEADER _WEIGHTS [ HEADER _CURRENT _CONTACTS ] = 1 ;
HEADER _WEIGHTS [ HEADER _UNGROUPED ] = 2 ;
2016-03-07 18:54:07 +01:00
HEADER _WEIGHTS [ HEADER _PENDING _CONTACTS ] = 3 ;
2016-07-26 08:00:30 +02:00
converse . RosterGroupsComparator = function ( a , b ) {
/ * G r o u p s a r e s o r t e d a l p h a b e t i c a l l y , i g n o r i n g c a s e .
* However , Ungrouped , Requesting Contacts and Pending Contacts
* appear last and in that order .
* /
a = a . get ( 'name' ) ;
b = b . get ( 'name' ) ;
var special _groups = _ . keys ( HEADER _WEIGHTS ) ;
var a _is _special = _ . contains ( special _groups , a ) ;
var b _is _special = _ . contains ( special _groups , b ) ;
if ( ! a _is _special && ! b _is _special ) {
return a . toLowerCase ( ) < b . toLowerCase ( ) ? - 1 : ( a . toLowerCase ( ) > b . toLowerCase ( ) ? 1 : 0 ) ;
} else if ( a _is _special && b _is _special ) {
return HEADER _WEIGHTS [ a ] < HEADER _WEIGHTS [ b ] ? - 1 : ( HEADER _WEIGHTS [ a ] > HEADER _WEIGHTS [ b ] ? 1 : 0 ) ;
} else if ( ! a _is _special && b _is _special ) {
return ( b === HEADER _REQUESTING _CONTACTS ) ? 1 : - 1 ;
} else if ( a _is _special && ! b _is _special ) {
return ( a === HEADER _REQUESTING _CONTACTS ) ? - 1 : 1 ;
}
} ;
2016-05-03 17:37:10 +02:00
converse . RosterFilter = Backbone . Model . extend ( {
initialize : function ( ) {
this . set ( {
'filter_text' : '' ,
'filter_type' : 'contacts' ,
'chat_state' : ''
} ) ;
} ,
} ) ;
2016-02-26 18:28:18 +01:00
2016-05-03 17:37:10 +02:00
converse . RosterFilterView = Backbone . View . extend ( {
tagName : 'span' ,
2016-03-07 18:54:07 +01:00
events : {
2016-03-16 12:49:35 +01:00
"keydown .roster-filter" : "liveFilter" ,
"click .onX" : "clearFilter" ,
2016-05-03 17:37:10 +02:00
"mousemove .x" : "toggleX" ,
"change .filter-type" : "changeTypeFilter" ,
"change .state-type" : "changeChatStateFilter"
} ,
initialize : function ( ) {
this . model . on ( 'change' , this . render , this ) ;
} ,
render : function ( ) {
this . $el . html ( converse . templates . roster (
_ . extend ( this . model . toJSON ( ) , {
placeholder : _ _ ( 'Filter' ) ,
label _contacts : LABEL _CONTACTS ,
label _groups : LABEL _GROUPS ,
label _state : _ _ ( 'State' ) ,
label _any : _ _ ( 'Any' ) ,
label _online : _ _ ( 'Online' ) ,
label _chatty : _ _ ( 'Chatty' ) ,
label _busy : _ _ ( 'Busy' ) ,
label _away : _ _ ( 'Away' ) ,
label _xa : _ _ ( 'Extended Away' ) ,
label _offline : _ _ ( 'Offline' )
} )
) ) ;
var $roster _filter = this . $ ( '.roster-filter' ) ;
$roster _filter [ this . tog ( $roster _filter . val ( ) ) ] ( 'x' ) ;
return this . $el ;
} ,
tog : function ( v ) {
return v ? 'addClass' : 'removeClass' ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-05-03 17:37:10 +02:00
toggleX : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var el = ev . target ;
$ ( el ) [ this . tog ( el . offsetWidth - 18 < ev . clientX - el . getBoundingClientRect ( ) . left ) ] ( 'onX' ) ;
} ,
changeChatStateFilter : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
this . model . save ( {
'chat_state' : this . $ ( '.state-type' ) . val ( )
} ) ;
} ,
changeTypeFilter : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var type = ev . target . value ;
if ( type === 'state' ) {
this . model . save ( {
'filter_type' : type ,
'chat_state' : this . $ ( '.state-type' ) . val ( )
} ) ;
} else {
this . model . save ( {
'filter_type' : type ,
'filter_text' : this . $ ( '.roster-filter' ) . val ( ) ,
} ) ;
}
} ,
liveFilter : _ . debounce ( function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
this . model . save ( {
'filter_type' : this . $ ( '.filter-type' ) . val ( ) ,
'filter_text' : this . $ ( '.roster-filter' ) . val ( )
} ) ;
} , 250 ) ,
2016-05-24 10:23:19 +02:00
isActive : function ( ) {
/ * R e t u r n s t r u e i f t h e f i l t e r i s e n a b l e d ( i . e . i f t h e u s e r
* has added values to the filter ) .
* /
if ( this . model . get ( 'filter_type' ) === 'state' ||
this . model . get ( 'filter_text' ) ) {
return true ;
}
return false ;
} ,
2016-05-03 17:37:10 +02:00
show : function ( ) {
if ( this . $el . is ( ':visible' ) ) { return this ; }
this . $el . show ( ) ;
return this ;
} ,
hide : function ( ) {
if ( ! this . $el . is ( ':visible' ) ) { return this ; }
if ( this . $ ( '.roster-filter' ) . val ( ) . length > 0 ) {
// Don't hide if user is currently filtering.
return ;
}
this . model . save ( {
'filter_text' : '' ,
'chat_state' : ''
} ) ;
this . $el . hide ( ) ;
return this ;
} ,
clearFilter : function ( ev ) {
if ( ev && ev . preventDefault ) {
ev . preventDefault ( ) ;
$ ( ev . target ) . removeClass ( 'x onX' ) . val ( '' ) ;
}
this . model . save ( {
'filter_text' : ''
} ) ;
}
} ) ;
converse . RosterView = Backbone . Overview . extend ( {
tagName : 'div' ,
id : 'converse-roster' ,
2016-03-07 18:54:07 +01:00
initialize : function ( ) {
2016-03-16 12:49:35 +01:00
this . roster _handler _ref = this . registerRosterHandler ( ) ;
this . rosterx _handler _ref = this . registerRosterXHandler ( ) ;
converse . roster . on ( "add" , this . onContactAdd , this ) ;
converse . roster . on ( 'change' , this . onContactChange , this ) ;
converse . roster . on ( "destroy" , this . update , this ) ;
converse . roster . on ( "remove" , this . update , this ) ;
this . model . on ( "add" , this . onGroupAdd , this ) ;
this . model . on ( "reset" , this . reset , this ) ;
2016-09-21 17:24:32 +02:00
converse . on ( 'rosterGroupsFetched' , this . positionFetchedGroups , this ) ;
converse . on ( 'rosterContactsFetched' , this . update , this ) ;
this . createRosterFilter ( ) ;
2016-05-03 17:37:10 +02:00
} ,
render : function ( ) {
2016-09-21 17:24:32 +02:00
this . $roster = $ ( '<dl class="roster-contacts" style="display: none;"></dl>' ) ;
2016-05-03 17:37:10 +02:00
this . $el . html ( this . filter _view . render ( ) ) ;
if ( ! converse . allow _contact _requests ) {
// XXX: if we ever support live editing of config then
// we'll need to be able to remove this class on the fly.
this . $el . addClass ( 'no-contact-requests' ) ;
}
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-09-21 17:24:32 +02:00
createRosterFilter : function ( ) {
// Create a model on which we can store filter properties
var model = new converse . RosterFilter ( ) ;
model . id = b64 _sha1 ( 'converse.rosterfilter' + converse . bare _jid ) ;
model . browserStorage = new Backbone . BrowserStorage . local ( this . filter . id ) ;
this . filter _view = new converse . RosterFilterView ( { 'model' : model } ) ;
this . filter _view . model . on ( 'change' , this . updateFilter , this ) ;
this . filter _view . model . fetch ( ) ;
} ,
2016-05-03 17:37:10 +02:00
updateFilter : _ . debounce ( function ( ) {
/ * F i l t e r t h e r o s t e r a g a i n .
* Called whenever the filter settings have been changed or
* when contacts have been added , removed or changed .
*
* Debounced so that it doesn ' t get called for every
* contact fetched from browser storage .
* /
var type = this . filter _view . model . get ( 'filter_type' ) ;
if ( type === 'state' ) {
this . filter ( this . filter _view . model . get ( 'chat_state' ) , type ) ;
} else {
this . filter ( this . filter _view . model . get ( 'filter_text' ) , type ) ;
}
} , 100 ) ,
2016-03-16 12:49:35 +01:00
unregisterHandlers : function ( ) {
converse . connection . deleteHandler ( this . roster _handler _ref ) ;
delete this . roster _handler _ref ;
converse . connection . deleteHandler ( this . rosterx _handler _ref ) ;
delete this . rosterx _handler _ref ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
update : _ . debounce ( function ( ) {
if ( this . $roster . parent ( ) . length === 0 ) {
this . $el . append ( this . $roster . show ( ) ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return this . showHideFilter ( ) ;
} , converse . animate ? 100 : 0 ) ,
2016-02-26 18:28:18 +01:00
2016-05-03 17:37:10 +02:00
showHideFilter : function ( ) {
if ( ! this . $el . is ( ':visible' ) ) {
return ;
}
if ( this . $roster . hasScrollBar ( ) ) {
this . filter _view . show ( ) ;
2016-05-24 10:23:19 +02:00
} else if ( ! this . filter _view . isActive ( ) ) {
2016-05-03 17:37:10 +02:00
this . filter _view . hide ( ) ;
2016-03-16 12:49:35 +01:00
}
2016-03-07 18:54:07 +01:00
return this ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
filter : function ( query , type ) {
2016-05-03 17:37:10 +02:00
// First we make sure the filter is restored to its
// original state
_ . each ( this . getAll ( ) , function ( view ) {
if ( view . model . contacts . length > 0 ) {
view . show ( ) . filter ( '' ) ;
}
} ) ;
// Now we can filter
2016-03-16 12:49:35 +01:00
query = query . toLowerCase ( ) ;
if ( type === 'groups' ) {
_ . each ( this . getAll ( ) , function ( view , idx ) {
if ( view . model . get ( 'name' ) . toLowerCase ( ) . indexOf ( query . toLowerCase ( ) ) === - 1 ) {
view . hide ( ) ;
} else if ( view . model . contacts . length > 0 ) {
view . show ( ) ;
}
} ) ;
} else {
_ . each ( this . getAll ( ) , function ( view ) {
view . filter ( query , type ) ;
} ) ;
2016-03-07 18:54:07 +01:00
}
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
reset : function ( ) {
converse . roster . reset ( ) ;
this . removeAll ( ) ;
this . $roster = $ ( '<dl class="roster-contacts" style="display: none;"></dl>' ) ;
this . render ( ) . update ( ) ;
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
registerRosterHandler : function ( ) {
converse . connection . addHandler (
converse . roster . onRosterPush . bind ( converse . roster ) ,
Strophe . NS . ROSTER , 'iq' , "set"
) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
registerRosterXHandler : function ( ) {
var t = 0 ;
converse . connection . addHandler (
function ( msg ) {
window . setTimeout (
function ( ) {
converse . connection . flush ( ) ;
converse . roster . subscribeToSuggestedItems . bind ( converse . roster ) ( msg ) ;
} ,
t
) ;
t += $ ( msg ) . find ( 'item' ) . length * 250 ;
return true ;
} ,
Strophe . NS . ROSTERX , 'message' , null
) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
onGroupAdd : function ( group ) {
var view = new converse . RosterGroupView ( { model : group } ) ;
this . add ( group . get ( 'name' ) , view . render ( ) ) ;
this . positionGroup ( view ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
onContactAdd : function ( contact ) {
this . addRosterContact ( contact ) . update ( ) ;
2016-05-03 17:37:10 +02:00
this . updateFilter ( ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
onContactChange : function ( contact ) {
this . updateChatBox ( contact ) . update ( ) ;
if ( _ . has ( contact . changed , 'subscription' ) ) {
if ( contact . changed . subscription === 'from' ) {
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
} else if ( _ . contains ( [ 'both' , 'to' ] , contact . get ( 'subscription' ) ) ) {
this . addExistingContact ( contact ) ;
}
}
if ( _ . has ( contact . changed , 'ask' ) && contact . changed . ask === 'subscribe' ) {
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
}
if ( _ . has ( contact . changed , 'subscription' ) && contact . changed . requesting === 'true' ) {
this . addContactToGroup ( contact , HEADER _REQUESTING _CONTACTS ) ;
2016-03-07 18:54:07 +01:00
}
2016-05-03 17:37:10 +02:00
this . updateFilter ( ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
updateChatBox : function ( contact ) {
var chatbox = converse . chatboxes . get ( contact . get ( 'jid' ) ) ,
changes = { } ;
if ( ! chatbox ) {
return this ;
}
if ( _ . has ( contact . changed , 'chat_status' ) ) {
changes . chat _status = contact . get ( 'chat_status' ) ;
}
if ( _ . has ( contact . changed , 'status' ) ) {
changes . status = contact . get ( 'status' ) ;
}
chatbox . save ( changes ) ;
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
positionFetchedGroups : function ( model , resp , options ) {
/ * I n s t e a d o f t h r o w i n g a n a d d e v e n t f o r e a c h g r o u p
2016-03-29 08:56:13 +02:00
* fetched , we wait until they ' re all fetched and then
* we position them .
* Works around the problem of positionGroup not
* working when all groups besides the one being
* positioned aren ' t already in inserted into the
* roster DOM element .
* /
2016-09-21 17:24:32 +02:00
this . model . sort ( ) ;
this . model . each ( function ( group , idx ) {
2016-03-16 12:49:35 +01:00
var view = this . get ( group . get ( 'name' ) ) ;
if ( ! view ) {
view = new converse . RosterGroupView ( { model : group } ) ;
this . add ( group . get ( 'name' ) , view . render ( ) ) ;
}
if ( idx === 0 ) {
this . $roster . append ( view . $el ) ;
} else {
this . appendGroup ( view ) ;
}
} . bind ( this ) ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
positionGroup : function ( view ) {
/ * P l a c e t h e g r o u p ' s D O M e l e m e n t i n t h e c o r r e c t a l p h a b e t i c a l
2016-03-29 08:56:13 +02:00
* position amongst the other groups in the roster .
* /
2016-03-16 12:49:35 +01:00
var $groups = this . $roster . find ( '.roster-group' ) ,
index = $groups . length ? this . model . indexOf ( view . model ) : 0 ;
if ( index === 0 ) {
this . $roster . prepend ( view . $el ) ;
} else if ( index === ( this . model . length - 1 ) ) {
this . appendGroup ( view ) ;
2016-03-07 18:54:07 +01:00
} else {
2016-03-16 12:49:35 +01:00
$ ( $groups . eq ( index ) ) . before ( view . $el ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return this ;
} ,
appendGroup : function ( view ) {
/ * A d d t h e g r o u p a t t h e b o t t o m o f t h e r o s t e r
2016-03-29 08:56:13 +02:00
* /
2016-03-16 12:49:35 +01:00
var $last = this . $roster . find ( '.roster-group' ) . last ( ) ;
var $siblings = $last . siblings ( 'dd' ) ;
if ( $siblings . length > 0 ) {
$siblings . last ( ) . after ( view . $el ) ;
} else {
$last . after ( view . $el ) ;
2016-03-07 18:54:07 +01:00
}
return this ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
getGroup : function ( name ) {
/ * R e t u r n s t h e g r o u p a s s p e c i f i e d b y n a m e .
2016-03-29 08:56:13 +02:00
* Creates the group if it doesn ' t exist .
* /
2016-03-16 12:49:35 +01:00
var view = this . get ( name ) ;
if ( view ) {
return view . model ;
}
return this . model . create ( { name : name , id : b64 _sha1 ( name ) } ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
addContactToGroup : function ( contact , name ) {
this . getGroup ( name ) . contacts . add ( contact ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
addExistingContact : function ( contact ) {
var groups ;
if ( converse . roster _groups ) {
groups = contact . get ( 'groups' ) ;
if ( groups . length === 0 ) {
groups = [ HEADER _UNGROUPED ] ;
}
} else {
groups = [ HEADER _CURRENT _CONTACTS ] ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
_ . each ( groups , _ . bind ( this . addContactToGroup , this , contact ) ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
addRosterContact : function ( contact ) {
if ( contact . get ( 'subscription' ) === 'both' || contact . get ( 'subscription' ) === 'to' ) {
this . addExistingContact ( contact ) ;
} else {
if ( ( contact . get ( 'ask' ) === 'subscribe' ) || ( contact . get ( 'subscription' ) === 'from' ) ) {
this . addContactToGroup ( contact , HEADER _PENDING _CONTACTS ) ;
} else if ( contact . get ( 'requesting' ) === true ) {
this . addContactToGroup ( contact , HEADER _REQUESTING _CONTACTS ) ;
}
}
return this ;
2016-03-07 18:54:07 +01:00
}
} ) ;
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
converse . RosterContactView = Backbone . View . extend ( {
tagName : 'dd' ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
events : {
"click .accept-xmpp-request" : "acceptRequest" ,
"click .decline-xmpp-request" : "declineRequest" ,
"click .open-chat" : "openChat" ,
"click .remove-xmpp-contact" : "removeContact"
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
initialize : function ( ) {
this . model . on ( "change" , this . render , this ) ;
this . model . on ( "remove" , this . remove , this ) ;
this . model . on ( "destroy" , this . remove , this ) ;
this . model . on ( "open" , this . openChat , this ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
render : function ( ) {
2016-05-03 17:37:10 +02:00
if ( ! this . mayBeShown ( ) ) {
2016-03-07 18:54:07 +01:00
this . $el . hide ( ) ;
return this ;
}
var item = this . model ,
ask = item . get ( 'ask' ) ,
chat _status = item . get ( 'chat_status' ) ,
requesting = item . get ( 'requesting' ) ,
subscription = item . get ( 'subscription' ) ;
var classes _to _remove = [
'current-xmpp-contact' ,
'pending-xmpp-contact' ,
'requesting-xmpp-contact'
] . concat ( _ . keys ( STATUSES ) ) ;
_ . each ( classes _to _remove ,
function ( cls ) {
if ( this . el . className . indexOf ( cls ) !== - 1 ) {
this . $el . removeClass ( cls ) ;
}
} , this ) ;
this . $el . addClass ( chat _status ) . data ( 'status' , chat _status ) ;
if ( ( ask === 'subscribe' ) || ( subscription === 'from' ) ) {
/ * a s k = = = ' s u b s c r i b e '
2016-03-29 08:56:13 +02:00
* Means we have asked to subscribe to them .
*
* subscription === 'from'
* They are subscribed to use , but not vice versa .
* We assume that there is a pending subscription
* from us to them ( otherwise we ' re in a state not
* supported by converse . js ) .
*
* So in both cases the user is a "pending" contact .
* /
2016-03-07 18:54:07 +01:00
this . $el . addClass ( 'pending-xmpp-contact' ) ;
this . $el . html ( converse . templates . pending _contact (
_ . extend ( item . toJSON ( ) , {
'desc_remove' : _ _ ( 'Click to remove this contact' ) ,
'allow_chat_pending_contacts' : converse . allow _chat _pending _contacts
} )
) ) ;
} else if ( requesting === true ) {
this . $el . addClass ( 'requesting-xmpp-contact' ) ;
this . $el . html ( converse . templates . requesting _contact (
_ . extend ( item . toJSON ( ) , {
'desc_accept' : _ _ ( "Click to accept this contact request" ) ,
'desc_decline' : _ _ ( "Click to decline this contact request" ) ,
'allow_chat_pending_contacts' : converse . allow _chat _pending _contacts
} )
) ) ;
converse . controlboxtoggle . showControlBox ( ) ;
} else if ( subscription === 'both' || subscription === 'to' ) {
this . $el . addClass ( 'current-xmpp-contact' ) ;
this . $el . removeClass ( _ . without ( [ 'both' , 'to' ] , subscription ) [ 0 ] ) . addClass ( subscription ) ;
this . $el . html ( converse . templates . roster _item (
_ . extend ( item . toJSON ( ) , {
'desc_status' : STATUSES [ chat _status || 'offline' ] ,
'desc_chat' : _ _ ( 'Click to chat with this contact' ) ,
'desc_remove' : _ _ ( 'Click to remove this contact' ) ,
'title_fullname' : _ _ ( 'Name' ) ,
'allow_contact_removal' : converse . allow _contact _removal
} )
) ) ;
}
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-05-03 17:37:10 +02:00
isGroupCollapsed : function ( ) {
/ * C h e c k w h e t h e r t h e g r o u p i n w h i c h t h i s c o n t a c t a p p e a r s i s
* collapsed .
* /
// XXX: this sucks and is fragile.
// It's because I tried to do the "right thing"
// and use definition lists to represent roster groups.
// If roster group items were inside the group elements, we
// would simplify things by not having to check whether the
// group is collapsed or not.
var name = this . $el . prevAll ( 'dt:first' ) . data ( 'group' ) ;
var group = converse . rosterview . model . where ( { 'name' : name } ) [ 0 ] ;
if ( group . get ( 'state' ) === converse . CLOSED ) {
return true ;
}
return false ;
} ,
mayBeShown : function ( ) {
/ * R e t u r n a b o o l e a n i n d i c a t i n g w h e t h e r t h i s c o n t a c t s h o u l d
* generally be visible in the roster .
*
* It doesn ' t check for the more specific case of whether
* the group it ' s in is collapsed ( see isGroupCollapsed ) .
* /
var chatStatus = this . model . get ( 'chat_status' ) ;
if ( ( converse . show _only _online _users && chatStatus !== 'online' ) ||
( converse . hide _offline _users && chatStatus === 'offline' ) ) {
// If pending or requesting, show
if ( ( this . model . get ( 'ask' ) === 'subscribe' ) ||
( this . model . get ( 'subscription' ) === 'from' ) ||
( this . model . get ( 'requesting' ) === true ) ) {
return true ;
}
return false ;
}
return true ;
} ,
2016-03-07 18:54:07 +01:00
openChat : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
return converse . chatboxviews . showChat ( this . model . attributes ) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
removeContact : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
if ( ! converse . allow _contact _removal ) { return ; }
var result = confirm ( _ _ ( "Are you sure you want to remove this contact?" ) ) ;
if ( result === true ) {
var iq = $iq ( { type : 'set' } )
. c ( 'query' , { xmlns : Strophe . NS . ROSTER } )
. c ( 'item' , { jid : this . model . get ( 'jid' ) , subscription : "remove" } ) ;
converse . connection . sendIQ ( iq ,
function ( iq ) {
this . model . destroy ( ) ;
this . remove ( ) ;
} . bind ( this ) ,
function ( err ) {
alert ( _ _ ( "Sorry, there was an error while trying to remove " + name + " as a contact." ) ) ;
converse . log ( err ) ;
}
) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
acceptRequest : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
converse . roster . sendContactAddIQ (
this . model . get ( 'jid' ) ,
this . model . get ( 'fullname' ) ,
[ ] ,
function ( ) { this . model . authorize ( ) . subscribe ( ) ; } . bind ( this )
) ;
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
declineRequest : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var result = confirm ( _ _ ( "Are you sure you want to decline this contact request?" ) ) ;
if ( result === true ) {
this . model . unauthorize ( ) . destroy ( ) ;
}
return this ;
2016-02-26 18:28:18 +01:00
}
2016-03-07 18:54:07 +01:00
} ) ;
converse . RosterGroupView = Backbone . Overview . extend ( {
tagName : 'dt' ,
className : 'roster-group' ,
events : {
"click a.group-toggle" : "toggle"
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
initialize : function ( ) {
this . model . contacts . on ( "add" , this . addContact , this ) ;
this . model . contacts . on ( "change:subscription" , this . onContactSubscriptionChange , this ) ;
this . model . contacts . on ( "change:requesting" , this . onContactRequestChange , this ) ;
this . model . contacts . on ( "change:chat_status" , function ( contact ) {
// This might be optimized by instead of first sorting,
// finding the correct position in positionContact
this . model . contacts . sort ( ) ;
this . positionContact ( contact ) . render ( ) ;
} , this ) ;
this . model . contacts . on ( "destroy" , this . onRemove , this ) ;
this . model . contacts . on ( "remove" , this . onRemove , this ) ;
converse . roster . on ( 'change:groups' , this . onContactGroupChange , this ) ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
render : function ( ) {
this . $el . attr ( 'data-group' , this . model . get ( 'name' ) ) ;
this . $el . html (
$ ( converse . templates . group _header ( {
label _group : this . model . get ( 'name' ) ,
desc _group _toggle : this . model . get ( 'description' ) ,
toggle _state : this . model . get ( 'state' )
} ) )
) ;
return this ;
} ,
addContact : function ( contact ) {
var view = new converse . RosterContactView ( { model : contact } ) ;
this . add ( contact . get ( 'id' ) , view ) ;
view = this . positionContact ( contact ) . render ( ) ;
2016-05-03 17:37:10 +02:00
if ( view . mayBeShown ( ) ) {
2016-03-07 18:54:07 +01:00
if ( this . model . get ( 'state' ) === converse . CLOSED ) {
if ( view . $el [ 0 ] . style . display !== "none" ) { view . $el . hide ( ) ; }
if ( ! this . $el . is ( ':visible' ) ) { this . $el . show ( ) ; }
} else {
if ( this . $el [ 0 ] . style . display !== "block" ) { this . show ( ) ; }
}
}
} ,
positionContact : function ( contact ) {
/ * P l a c e t h e c o n t a c t ' s D O M e l e m e n t i n t h e c o r r e c t a l p h a b e t i c a l
2016-03-29 08:56:13 +02:00
* position amongst the other contacts in this group .
* /
2016-03-07 18:54:07 +01:00
var view = this . get ( contact . get ( 'id' ) ) ;
var index = this . model . contacts . indexOf ( contact ) ;
view . $el . detach ( ) ;
if ( index === 0 ) {
this . $el . after ( view . $el ) ;
} else if ( index === ( this . model . contacts . length - 1 ) ) {
this . $el . nextUntil ( 'dt' ) . last ( ) . after ( view . $el ) ;
} else {
this . $el . nextUntil ( 'dt' ) . eq ( index ) . before ( view . $el ) ;
}
return view ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
show : function ( ) {
this . $el . show ( ) ;
2016-05-03 17:37:10 +02:00
_ . each ( this . getAll ( ) , function ( view ) {
if ( view . mayBeShown ( ) && ! view . isGroupCollapsed ( ) ) {
view . $el . show ( ) ;
2016-03-07 18:54:07 +01:00
}
} ) ;
2016-05-03 17:37:10 +02:00
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
hide : function ( ) {
this . $el . nextUntil ( 'dt' ) . addBack ( ) . hide ( ) ;
} ,
2016-02-28 10:00:22 +01:00
2016-05-03 17:37:10 +02:00
filter : function ( q , type ) {
2016-03-07 18:54:07 +01:00
/ * F i l t e r t h e g r o u p ' s c o n t a c t s b a s e d o n t h e q u e r y " q " .
2016-03-29 08:56:13 +02:00
* The query is matched against the contact ' s full name .
* If all contacts are filtered out ( i . e . hidden ) , then the
* group must be filtered out as well .
* /
2016-03-07 18:54:07 +01:00
var matches ;
if ( q . length === 0 ) {
if ( this . model . get ( 'state' ) === converse . OPENED ) {
this . model . contacts . each ( function ( item ) {
2016-05-03 17:37:10 +02:00
var view = this . get ( item . get ( 'id' ) ) ;
if ( view . mayBeShown ( ) && ! view . isGroupCollapsed ( ) ) {
view . $el . show ( ) ;
2016-03-07 18:54:07 +01:00
}
} . bind ( this ) ) ;
}
this . showIfNecessary ( ) ;
} else {
q = q . toLowerCase ( ) ;
2016-05-03 17:37:10 +02:00
if ( type === 'state' ) {
2016-07-26 08:00:30 +02:00
if ( this . model . get ( 'name' ) === HEADER _REQUESTING _CONTACTS ) {
// When filtering by chat state, we still want to
// show requesting contacts, even though they don't
// have the state in question.
matches = this . model . contacts . filter (
function ( contact ) {
return utils . contains . not ( 'chat_status' , q ) ( contact ) && ! contact . get ( 'requesting' ) ;
}
) ;
} else {
matches = this . model . contacts . filter (
utils . contains . not ( 'chat_status' , q )
) ;
}
2016-05-03 17:37:10 +02:00
} else {
matches = this . model . contacts . filter (
utils . contains . not ( 'fullname' , q )
) ;
}
if ( matches . length === this . model . contacts . length ) {
// hide the whole group
2016-03-07 18:54:07 +01:00
this . hide ( ) ;
} else {
_ . each ( matches , function ( item ) {
this . get ( item . get ( 'id' ) ) . $el . hide ( ) ;
} . bind ( this ) ) ;
_ . each ( this . model . contacts . reject ( utils . contains . not ( 'fullname' , q ) ) , function ( item ) {
this . get ( item . get ( 'id' ) ) . $el . show ( ) ;
} . bind ( this ) ) ;
this . showIfNecessary ( ) ;
}
}
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
showIfNecessary : function ( ) {
if ( ! this . $el . is ( ':visible' ) && this . model . contacts . length > 0 ) {
this . $el . show ( ) ;
}
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
toggle : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var $el = $ ( ev . target ) ;
if ( $el . hasClass ( "icon-opened" ) ) {
this . $el . nextUntil ( 'dt' ) . slideUp ( ) ;
this . model . save ( { state : converse . CLOSED } ) ;
$el . removeClass ( "icon-opened" ) . addClass ( "icon-closed" ) ;
} else {
$el . removeClass ( "icon-closed" ) . addClass ( "icon-opened" ) ;
this . model . save ( { state : converse . OPENED } ) ;
this . filter (
2016-05-03 17:37:10 +02:00
converse . rosterview . $ ( '.roster-filter' ) . val ( ) || '' ,
2016-03-07 18:54:07 +01:00
converse . rosterview . $ ( '.filter-type' ) . val ( )
) ;
}
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
onContactGroupChange : function ( contact ) {
var in _this _group = _ . contains ( contact . get ( 'groups' ) , this . model . get ( 'name' ) ) ;
var cid = contact . get ( 'id' ) ;
var in _this _overview = ! this . get ( cid ) ;
if ( in _this _group && ! in _this _overview ) {
this . model . contacts . remove ( cid ) ;
} else if ( ! in _this _group && in _this _overview ) {
this . addContact ( contact ) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
onContactSubscriptionChange : function ( contact ) {
if ( ( this . model . get ( 'name' ) === HEADER _PENDING _CONTACTS ) && contact . get ( 'subscription' ) !== 'from' ) {
this . model . contacts . remove ( contact . get ( 'id' ) ) ;
}
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
onContactRequestChange : function ( contact ) {
if ( ( this . model . get ( 'name' ) === HEADER _REQUESTING _CONTACTS ) && ! contact . get ( 'requesting' ) ) {
2016-07-28 18:06:31 +02:00
/ * W e s u p p r e s s e v e n t s , o t h e r w i s e t h e r e m o v e e v e n t w i l l
* also cause the contact ' s view to be removed from the
* "Pending Contacts" group .
* /
this . model . contacts . remove ( contact . get ( 'id' ) , { 'silent' : true } ) ;
// Since we suppress events, we make sure the view and
// contact are removed from this group.
this . get ( contact . get ( 'id' ) ) . remove ( ) ;
this . onRemove ( contact ) ;
2016-03-07 18:54:07 +01:00
}
} ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
onRemove : function ( contact ) {
this . remove ( contact . get ( 'id' ) ) ;
if ( this . model . contacts . length === 0 ) {
this . $el . hide ( ) ;
}
}
} ) ;
2016-03-16 12:49:35 +01:00
}
} ) ;
} ) ) ;
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define, Backbone */
( function ( root , factory ) {
define ( "converse-controlbox" , [
"converse-core" ,
"converse-api" ,
2016-07-26 08:00:30 +02:00
// TODO: The next two dependencies can be made optional
2016-03-16 12:49:35 +01:00
"converse-rosterview" ,
"converse-chatview"
] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
// Strophe methods for building stanzas
var Strophe = converse _api . env . Strophe ,
utils = converse _api . env . utils ;
// Other necessary globals
var $ = converse _api . env . jQuery ,
_ = converse _api . env . _ ,
_ _ = utils . _ _ . bind ( converse ) ,
moment = converse _api . env . moment ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-controlbox' , {
2016-03-16 12:49:35 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
initSession : function ( ) {
this . controlboxtoggle = new this . ControlBoxToggle ( ) ;
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initSession . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
} ,
initConnection : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initConnection . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
if ( this . connection ) {
this . addControlBox ( ) ;
}
} ,
2016-04-01 15:33:14 +02:00
onDisconnected : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . onDisconnected . apply ( this , arguments ) ;
// Set connected to `false`, so that if we reconnect,
// "onConnected" will be called, to fetch the roster again and
// to send out a presence stanza.
var view = converse . chatboxviews . get ( 'controlbox' ) ;
view . model . set ( { connected : false } ) ;
// If we're not going to reconnect, then render the login
// panel.
2016-05-03 17:37:10 +02:00
if ( result === 'disconnected' ) {
view . $ ( '#controlbox-tabs' ) . empty ( ) ;
view . renderLoginPanel ( ) ;
2016-04-01 15:33:14 +02:00
}
2016-06-20 21:11:43 +02:00
return result ;
2016-04-01 15:33:14 +02:00
} ,
2016-09-16 14:35:02 +02:00
afterReconnected : function ( ) {
this . _ _super _ _ . afterReconnected . apply ( this , arguments ) ;
var view = converse . chatboxviews . get ( 'controlbox' ) ;
if ( view . model . get ( 'connected' ) ) {
converse . chatboxviews . get ( "controlbox" ) . onConnected ( ) ;
} else {
view . model . set ( { connected : true } ) ;
}
} ,
2016-03-16 12:49:35 +01:00
_tearDown : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . _tearDown . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
if ( this . rosterview ) {
this . rosterview . unregisterHandlers ( ) ;
// Removes roster groups
this . rosterview . model . off ( ) . reset ( ) ;
this . rosterview . undelegateEvents ( ) . remove ( ) ;
}
} ,
clearSession : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . clearSession . apply ( this , arguments ) ;
2016-05-03 17:37:10 +02:00
if ( typeof this . connection !== 'undefined' && this . connection . connected ) {
2016-03-16 12:49:35 +01:00
this . chatboxes . get ( 'controlbox' ) . save ( { 'connected' : false } ) ;
}
} ,
ChatBoxes : {
2016-05-03 17:37:10 +02:00
chatBoxMayBeShown : function ( chatbox ) {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . chatBoxMayBeShown . apply ( this , arguments ) &&
2016-03-16 12:49:35 +01:00
chatbox . get ( 'id' ) !== 'controlbox' ;
} ,
onChatBoxesFetched : function ( collection , resp ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . onChatBoxesFetched . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
if ( ! _ . include ( _ . pluck ( resp , 'id' ) , 'controlbox' ) ) {
this . add ( {
id : 'controlbox' ,
box _id : 'controlbox'
} ) ;
}
this . get ( 'controlbox' ) . save ( { connected : true } ) ;
} ,
} ,
ChatBoxViews : {
onChatBoxAdded : function ( item ) {
2016-03-30 14:03:15 +02:00
if ( item . get ( 'box_id' ) === 'controlbox' ) {
var view = this . get ( item . get ( 'id' ) ) ;
if ( view ) {
view . model = item ;
view . initialize ( ) ;
return view ;
} else {
view = new converse . ControlBoxView ( { model : item } ) ;
return this . add ( item . get ( 'id' ) , view ) ;
}
2016-03-16 12:49:35 +01:00
} else {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . onChatBoxAdded . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
}
} ,
closeAllChatBoxes : function ( ) {
this . each ( function ( view ) {
if ( view . model . get ( 'id' ) !== 'controlbox' ) {
view . close ( ) ;
}
} ) ;
return this ;
} ,
getChatBoxWidth : function ( view ) {
var controlbox = this . get ( 'controlbox' ) ;
if ( view . model . get ( 'id' ) === 'controlbox' ) {
/ * W e r e t u r n t h e w i d t h o f t h e c o n t r o l b o x o r i t s t o g g l e ,
* depending on which is visible .
* /
if ( ! controlbox || ! controlbox . $el . is ( ':visible' ) ) {
return converse . controlboxtoggle . $el . outerWidth ( true ) ;
} else {
return controlbox . $el . outerWidth ( true ) ;
}
} else {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . getChatBoxWidth . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
}
}
} ,
ChatBox : {
initialize : function ( ) {
if ( this . get ( 'id' ) === 'controlbox' ) {
2016-04-01 15:33:14 +02:00
this . set ( {
'time_opened' : moment ( 0 ) . valueOf ( ) ,
'num_unread' : 0
} ) ;
2016-03-16 12:49:35 +01:00
} else {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
}
} ,
} ,
ChatBoxView : {
2016-07-26 08:00:30 +02:00
insertIntoDOM : function ( ) {
2016-03-16 12:49:35 +01:00
this . $el . insertAfter ( converse . chatboxviews . get ( "controlbox" ) . $el ) ;
return this ;
}
}
} ,
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
var converse = this . converse ;
this . updateSettings ( {
2016-03-19 14:33:31 +01:00
allow _logout : true ,
default _domain : undefined ,
2016-03-16 12:49:35 +01:00
show _controlbox _by _default : false ,
2016-04-01 15:33:14 +02:00
sticky _controlbox : false ,
2016-03-19 14:33:31 +01:00
xhr _user _search : false ,
xhr _user _search _url : ''
2016-03-16 12:49:35 +01:00
} ) ;
var LABEL _CONTACTS = _ _ ( 'Contacts' ) ;
converse . addControlBox = function ( ) {
return converse . chatboxes . add ( {
id : 'controlbox' ,
box _id : 'controlbox' ,
closed : ! converse . show _controlbox _by _default
} ) ;
} ;
converse . ControlBoxView = converse . ChatBoxView . extend ( {
2016-03-07 18:54:07 +01:00
tagName : 'div' ,
2016-03-16 12:49:35 +01:00
className : 'chatbox' ,
id : 'controlbox' ,
2016-03-07 18:54:07 +01:00
events : {
2016-03-16 12:49:35 +01:00
'click a.close-chatbox-button' : 'close' ,
'click ul#controlbox-tabs li a' : 'switchTab' ,
} ,
initialize : function ( ) {
this . $el . insertAfter ( converse . controlboxtoggle . $el ) ;
this . model . on ( 'change:connected' , this . onConnected , this ) ;
this . model . on ( 'destroy' , this . hide , this ) ;
this . model . on ( 'hide' , this . hide , this ) ;
this . model . on ( 'show' , this . show , this ) ;
this . model . on ( 'change:closed' , this . ensureClosedState , this ) ;
this . render ( ) ;
if ( this . model . get ( 'connected' ) ) {
2016-09-21 17:24:32 +02:00
this . insertRoster ( ) ;
2016-03-16 12:49:35 +01:00
}
if ( typeof this . model . get ( 'closed' ) === 'undefined' ) {
this . model . set ( 'closed' , ! converse . show _controlbox _by _default ) ;
}
if ( ! this . model . get ( 'closed' ) ) {
this . show ( ) ;
} else {
this . hide ( ) ;
}
} ,
render : function ( ) {
2016-04-01 15:33:14 +02:00
this . $el . html ( converse . templates . controlbox (
_ . extend ( this . model . toJSON ( ) , {
sticky _controlbox : converse . sticky _controlbox
} ) )
) ;
2016-03-16 12:49:35 +01:00
if ( ! converse . connection . connected || ! converse . connection . authenticated || converse . connection . disconnecting ) {
this . renderLoginPanel ( ) ;
} else if ( ! this . contactspanel || ! this . contactspanel . $el . is ( ':visible' ) ) {
this . renderContactsPanel ( ) ;
}
return this ;
} ,
giveFeedback : function ( message , klass ) {
var $el = this . $ ( '.conn-feedback' ) ;
$el . addClass ( 'conn-feedback' ) . text ( message ) ;
if ( klass ) {
$el . addClass ( klass ) ;
}
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
onConnected : function ( ) {
if ( this . model . get ( 'connected' ) ) {
2016-09-21 17:24:32 +02:00
this . render ( ) . insertRoster ( ) ;
2016-03-16 12:49:35 +01:00
}
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-09-21 17:24:32 +02:00
insertRoster : function ( ) {
/ * P l a c e t h e r o s t e r v i e w i n s i d e t h e " C o n t a c t s " p a n e l .
2016-03-29 08:56:13 +02:00
* /
2016-03-16 12:49:35 +01:00
this . contactspanel . $el . append ( converse . rosterview . $el ) ;
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-03-16 12:49:35 +01:00
renderLoginPanel : function ( ) {
var $feedback = this . $ ( '.conn-feedback' ) ; // we want to still show any existing feedback.
2016-05-03 17:37:10 +02:00
this . loginpanel = new converse . LoginPanel ( {
2016-03-16 12:49:35 +01:00
'$parent' : this . $el . find ( '.controlbox-panes' ) ,
'model' : this
2016-05-03 17:37:10 +02:00
} ) ;
2016-03-16 12:49:35 +01:00
this . loginpanel . render ( ) ;
if ( $feedback . length && $feedback . text ( ) !== _ _ ( 'Connecting' ) ) {
this . $ ( '.conn-feedback' ) . replaceWith ( $feedback ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
renderContactsPanel : function ( ) {
this . contactspanel = new converse . ContactsPanel ( {
'$parent' : this . $el . find ( '.controlbox-panes' )
} ) ;
this . contactspanel . render ( ) ;
converse . xmppstatusview = new converse . XMPPStatusView ( {
'model' : converse . xmppstatus
} ) ;
converse . xmppstatusview . render ( ) ;
} ,
close : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
if ( converse . connection . connected ) {
this . model . save ( { 'closed' : true } ) ;
} else {
this . model . trigger ( 'hide' ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
converse . emit ( 'controlBoxClosed' , this ) ;
2016-03-07 18:54:07 +01:00
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
ensureClosedState : function ( ) {
if ( this . model . get ( 'closed' ) ) {
this . hide ( ) ;
} else {
this . show ( ) ;
}
} ,
hide : function ( callback ) {
this . $el . hide ( 'fast' , function ( ) {
utils . refreshWebkit ( ) ;
converse . emit ( 'chatBoxClosed' , this ) ;
converse . controlboxtoggle . show ( function ( ) {
if ( typeof callback === "function" ) {
callback ( ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
} ) ;
2016-03-07 18:54:07 +01:00
} ) ;
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
onControlBoxToggleHidden : function ( ) {
this . $el . show ( 'fast' , function ( ) {
2016-05-03 17:37:10 +02:00
converse . controlboxtoggle . updateOnlineCount ( ) ;
2016-03-16 12:49:35 +01:00
utils . refreshWebkit ( ) ;
converse . emit ( 'controlBoxOpened' , this ) ;
} . bind ( this ) ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
show : function ( ) {
converse . controlboxtoggle . hide (
this . onControlBoxToggleHidden . bind ( this )
) ;
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
switchTab : function ( ev ) {
// TODO: automatically focus the relevant input
2016-03-07 18:54:07 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2016-03-16 12:49:35 +01:00
var $tab = $ ( ev . target ) ,
$sibling = $tab . parent ( ) . siblings ( 'li' ) . children ( 'a' ) ,
$tab _panel = $ ( $tab . attr ( 'href' ) ) ;
$ ( $sibling . attr ( 'href' ) ) . hide ( ) ;
$sibling . removeClass ( 'current' ) ;
$tab . addClass ( 'current' ) ;
$tab _panel . show ( ) ;
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
showHelpMessages : function ( msgs ) {
// Override showHelpMessages in ChatBoxView, for now do nothing.
return ;
}
} ) ;
converse . LoginPanel = Backbone . View . extend ( {
tagName : 'div' ,
id : "login-dialog" ,
className : 'controlbox-pane' ,
events : {
'submit form#converse-login' : 'authenticate'
} ,
initialize : function ( cfg ) {
cfg . $parent . html ( this . $el . html (
converse . templates . login _panel ( {
'ANONYMOUS' : converse . ANONYMOUS ,
2016-09-16 14:35:02 +02:00
'EXTERNAL' : converse . EXTERNAL ,
'LOGIN' : converse . LOGIN ,
2016-03-16 12:49:35 +01:00
'PREBIND' : converse . PREBIND ,
'auto_login' : converse . auto _login ,
'authentication' : converse . authentication ,
'label_username' : _ _ ( 'XMPP Username:' ) ,
'label_password' : _ _ ( 'Password:' ) ,
'label_anon_login' : _ _ ( 'Click here to log in anonymously' ) ,
'label_login' : _ _ ( 'Log In' ) ,
'placeholder_username' : ( converse . locked _domain || converse . default _domain ) && _ _ ( 'Username' ) || _ _ ( 'user@server' ) ,
'placeholder_password' : _ _ ( 'password' )
} )
) ) ;
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
} ,
render : function ( ) {
this . $tabs . append ( converse . templates . login _tab ( { label _sign _in : _ _ ( 'Sign in' ) } ) ) ;
this . $el . find ( 'input#jid' ) . focus ( ) ;
if ( ! this . $el . is ( ':visible' ) ) {
this . $el . show ( ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-03-16 12:49:35 +01:00
authenticate : function ( ev ) {
2016-03-07 18:54:07 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2016-03-16 12:49:35 +01:00
var $form = $ ( ev . target ) ;
if ( converse . authentication === converse . ANONYMOUS ) {
this . connect ( $form , converse . jid , null ) ;
return ;
}
var $jid _input = $form . find ( 'input[name=jid]' ) ,
jid = $jid _input . val ( ) ,
$pw _input = $form . find ( 'input[name=password]' ) ,
password = $pw _input . val ( ) ,
errors = false ;
2016-03-07 18:54:07 +01:00
2016-09-16 14:35:02 +02:00
if ( ! jid ) {
2016-03-16 12:49:35 +01:00
errors = true ;
$jid _input . addClass ( 'error' ) ;
2016-03-07 18:54:07 +01:00
}
2016-09-16 14:35:02 +02:00
if ( ! password && converse . authentication !== converse . EXTERNAL ) {
2016-03-16 12:49:35 +01:00
errors = true ;
$pw _input . addClass ( 'error' ) ;
}
if ( errors ) { return ; }
if ( converse . locked _domain ) {
jid = Strophe . escapeNode ( jid ) + '@' + converse . locked _domain ;
} else if ( converse . default _domain && jid . indexOf ( '@' ) === - 1 ) {
jid = jid + '@' + converse . default _domain ;
}
this . connect ( $form , jid , password ) ;
return false ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
connect : function ( $form , jid , password ) {
var resource ;
if ( $form ) {
$form . find ( 'input[type=submit]' ) . hide ( ) . after ( '<span class="spinner login-submit"/>' ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
if ( jid ) {
resource = Strophe . getResourceFromJid ( jid ) ;
if ( ! resource ) {
jid = jid . toLowerCase ( ) + converse . generateResource ( ) ;
} else {
jid = Strophe . getBareJidFromJid ( jid ) . toLowerCase ( ) + '/' + resource ;
2016-03-07 18:54:07 +01:00
}
}
2016-03-16 12:49:35 +01:00
converse . connection . connect ( jid , password , converse . onConnectStatusChanged ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
remove : function ( ) {
this . $tabs . empty ( ) ;
this . $el . parent ( ) . empty ( ) ;
}
} ) ;
converse . 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"
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
initialize : function ( ) {
this . model . on ( "change:status" , this . updateStatusUI , this ) ;
this . model . on ( "change:status_message" , this . updateStatusUI , this ) ;
this . model . on ( "update-status-ui" , this . updateStatusUI , this ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
render : function ( ) {
// Replace the default dropdown with something nicer
var $select = this . $el . find ( 'select#select-xmpp-status' ) ,
chat _status = this . model . get ( 'status' ) || 'offline' ,
options = $ ( 'option' , $select ) ,
$options _target ,
options _list = [ ] ;
this . $el . html ( converse . templates . choose _status ( ) ) ;
this . $el . find ( '#fancy-xmpp-status-select' )
. html ( converse . templates . chat _status ( {
'status_message' : this . model . get ( 'status_message' ) || _ _ ( "I am %1$s" , this . getPrettyStatus ( chat _status ) ) ,
'chat_status' : chat _status ,
'desc_custom_status' : _ _ ( 'Click here to write a custom status message' ) ,
'desc_change_status' : _ _ ( 'Click to change your chat status' )
} ) ) ;
// iterate through all the <option> elements and add option values
options . each ( function ( ) {
options _list . push ( converse . templates . status _option ( {
'value' : $ ( this ) . val ( ) ,
'text' : this . text
} ) ) ;
} ) ;
$options _target = this . $el . find ( "#target dd ul" ) . hide ( ) ;
$options _target . append ( options _list . join ( '' ) ) ;
$select . remove ( ) ;
return this ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
toggleOptions : function ( ev ) {
ev . preventDefault ( ) ;
$ ( ev . target ) . parent ( ) . parent ( ) . siblings ( 'dd' ) . find ( 'ul' ) . toggle ( 'fast' ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
renderStatusChangeForm : function ( ev ) {
ev . preventDefault ( ) ;
var status _message = this . model . get ( 'status' ) || 'offline' ;
var input = converse . templates . change _status _message ( {
'status_message' : status _message ,
'label_custom_status' : _ _ ( 'Custom status' ) ,
'label_save' : _ _ ( 'Save' )
} ) ;
var $xmppstatus = this . $el . find ( '.xmpp-status' ) ;
$xmppstatus . parent ( ) . addClass ( 'no-border' ) ;
$xmppstatus . replaceWith ( input ) ;
this . $el . find ( '.custom-xmpp-status' ) . focus ( ) . focus ( ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
setStatusMessage : function ( ev ) {
ev . preventDefault ( ) ;
this . model . setStatusMessage ( $ ( ev . target ) . find ( 'input' ) . val ( ) ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
setStatus : function ( ev ) {
ev . preventDefault ( ) ;
var $el = $ ( ev . currentTarget ) ,
value = $el . attr ( 'data-value' ) ;
if ( value === 'logout' ) {
this . $el . find ( ".dropdown dd ul" ) . hide ( ) ;
converse . logOut ( ) ;
} else {
this . model . setStatus ( value ) ;
this . $el . find ( ".dropdown dd ul" ) . hide ( ) ;
2016-03-07 18:54:07 +01:00
}
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
getPrettyStatus : function ( stat ) {
if ( stat === 'chat' ) {
return _ _ ( 'online' ) ;
} else if ( stat === 'dnd' ) {
return _ _ ( 'busy' ) ;
} else if ( stat === 'xa' ) {
return _ _ ( 'away for long' ) ;
} else if ( stat === 'away' ) {
return _ _ ( 'away' ) ;
} else if ( stat === 'offline' ) {
return _ _ ( 'offline' ) ;
} else {
return _ _ ( stat ) || _ _ ( 'online' ) ;
2016-03-07 18:54:07 +01:00
}
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
updateStatusUI : function ( model ) {
var stat = model . get ( 'status' ) ;
// For translators: the %1$s part gets replaced with the status
// Example, I am online
var status _message = model . get ( 'status_message' ) || _ _ ( "I am %1$s" , this . getPrettyStatus ( stat ) ) ;
this . $el . find ( '#fancy-xmpp-status-select' ) . removeClass ( 'no-border' ) . html (
converse . templates . chat _status ( {
'chat_status' : stat ,
'status_message' : status _message ,
'desc_custom_status' : _ _ ( 'Click here to write a custom status message' ) ,
'desc_change_status' : _ _ ( 'Click to change your chat status' )
} ) ) ;
}
} ) ;
converse . ContactsPanel = Backbone . View . extend ( {
tagName : 'div' ,
className : 'controlbox-pane' ,
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'
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
initialize : function ( cfg ) {
cfg . $parent . append ( this . $el ) ;
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
} ,
render : function ( ) {
var markup ;
var widgets = converse . templates . contacts _panel ( {
label _online : _ _ ( 'Online' ) ,
label _busy : _ _ ( 'Busy' ) ,
label _away : _ _ ( 'Away' ) ,
label _offline : _ _ ( 'Offline' ) ,
label _logout : _ _ ( 'Log out' ) ,
include _offline _state : converse . include _offline _state ,
allow _logout : converse . allow _logout
} ) ;
this . $tabs . append ( converse . templates . contacts _tab ( { label _contacts : LABEL _CONTACTS } ) ) ;
if ( converse . xhr _user _search ) {
markup = converse . templates . search _contact ( {
label _contact _name : _ _ ( 'Contact name' ) ,
label _search : _ _ ( 'Search' )
} ) ;
2016-03-07 18:54:07 +01:00
} else {
2016-03-16 12:49:35 +01:00
markup = converse . templates . add _contact _form ( {
2016-05-03 17:37:10 +02:00
label _contact _username : _ _ ( 'e.g. user@example.org' ) ,
2016-03-16 12:49:35 +01:00
label _add : _ _ ( 'Add' )
} ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
if ( converse . allow _contact _requests ) {
widgets += converse . templates . add _contact _dropdown ( {
label _click _to _chat : _ _ ( 'Click to add new chat contacts' ) ,
label _add _contact : _ _ ( 'Add a contact' )
} ) ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
this . $el . html ( widgets ) ;
this . $el . find ( '.search-xmpp ul' ) . append ( markup ) ;
2016-03-07 18:54:07 +01:00
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
toggleContactForm : function ( ev ) {
ev . preventDefault ( ) ;
this . $el . find ( '.search-xmpp' ) . toggle ( 'fast' , function ( ) {
if ( $ ( this ) . is ( ':visible' ) ) {
$ ( this ) . find ( 'input.username' ) . focus ( ) ;
}
} ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
searchContacts : function ( ev ) {
ev . preventDefault ( ) ;
$ . getJSON ( converse . xhr _user _search _url + "?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 . getNodeFromJid ( obj . id ) + "@" + Strophe . getDomainFromJid ( obj . id ) )
. text ( obj . fullname )
)
) ;
} ) ;
} ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01: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 ;
2016-03-07 18:54:07 +01:00
}
2016-03-16 12:49:35 +01:00
converse . roster . addAndSubscribe ( jid ) ;
$ ( '.search-xmpp' ) . hide ( ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-16 12:49:35 +01:00
addContactFromList : function ( ev ) {
ev . preventDefault ( ) ;
var $target = $ ( ev . target ) ,
jid = $target . attr ( 'data-recipient' ) ,
name = $target . text ( ) ;
converse . roster . addAndSubscribe ( jid , name ) ;
$target . parent ( ) . remove ( ) ;
$ ( '.search-xmpp' ) . hide ( ) ;
2016-02-28 10:00:22 +01:00
}
2016-03-07 18:54:07 +01:00
} ) ;
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
converse . ControlBoxToggle = Backbone . View . extend ( {
tagName : 'a' ,
className : 'toggle-controlbox' ,
id : 'toggle-controlbox' ,
events : {
'click' : 'onClick'
} ,
attributes : {
'href' : "#"
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
initialize : function ( ) {
this . render ( ) ;
2016-06-20 21:11:43 +02:00
this . updateOnlineCount ( ) ;
2016-05-03 17:37:10 +02:00
converse . on ( 'initialized' , function ( ) {
converse . roster . on ( "add" , this . updateOnlineCount , this ) ;
converse . roster . on ( 'change' , this . updateOnlineCount , this ) ;
converse . roster . on ( "destroy" , this . updateOnlineCount , this ) ;
converse . roster . on ( "remove" , this . updateOnlineCount , this ) ;
} . bind ( this ) ) ;
2016-03-07 18:54:07 +01:00
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
render : function ( ) {
$ ( '#conversejs' ) . prepend ( this . $el . html (
converse . templates . controlbox _toggle ( {
'label_toggle' : _ _ ( 'Toggle chat' )
} )
) ) ;
// We let the render method of ControlBoxView decide whether
// the ControlBox or the Toggle must be shown. This prevents
// artifacts (i.e. on page load the toggle is shown only to then
// seconds later be hidden in favor of the control box).
this . $el . hide ( ) ;
return this ;
} ,
2016-02-28 10:00:22 +01:00
2016-05-03 17:37:10 +02:00
updateOnlineCount : _ . debounce ( function ( ) {
if ( typeof converse . roster === 'undefined' ) {
return ;
}
var $count = this . $ ( '#online-count' ) ;
$count . text ( '(' + converse . roster . getNumOnlineContacts ( ) + ')' ) ;
if ( ! $count . is ( ':visible' ) ) {
$count . show ( ) ;
}
} , converse . animate ? 100 : 0 ) ,
2016-03-07 18:54:07 +01:00
hide : function ( callback ) {
this . $el . fadeOut ( 'fast' , callback ) ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
show : function ( callback ) {
this . $el . show ( 'fast' , callback ) ;
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
showControlBox : function ( ) {
var controlbox = converse . chatboxes . get ( 'controlbox' ) ;
if ( ! controlbox ) {
controlbox = converse . addControlBox ( ) ;
}
if ( converse . connection . connected ) {
controlbox . save ( { closed : false } ) ;
} else {
controlbox . trigger ( 'show' ) ;
}
} ,
2016-02-28 10:00:22 +01:00
2016-03-07 18:54:07 +01:00
onClick : function ( e ) {
e . preventDefault ( ) ;
if ( $ ( "div#controlbox" ) . is ( ':visible' ) ) {
var controlbox = converse . chatboxes . get ( 'controlbox' ) ;
if ( converse . connection . connected ) {
controlbox . save ( { closed : true } ) ;
} else {
controlbox . trigger ( 'hide' ) ;
}
} else {
this . showControlBox ( ) ;
}
}
} ) ;
}
} ) ;
} ) ) ;
2016-02-26 18:28:18 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-03-29 08:56:13 +02:00
/*global Backbone, define, window */
2016-02-26 18:28:18 +01:00
/ * T h i s i s a C o n v e r s e . j s p l u g i n w h i c h a d d s u p p o r t f o r m u l t i - u s e r c h a t r o o m s , a s
* specified in XEP - 0045 Multi - user chat .
* /
( function ( root , factory ) {
2016-03-07 18:54:07 +01:00
define ( "converse-muc" , [
"converse-core" ,
"converse-api" ,
2016-03-16 12:49:35 +01:00
"typeahead" ,
2016-06-20 21:11:43 +02:00
"converse-chatview"
2016-03-07 18:54:07 +01:00
] , factory ) ;
2016-02-26 18:28:18 +01:00
} ( this , function ( converse , converse _api ) {
"use strict" ;
// Strophe methods for building stanzas
var Strophe = converse _api . env . Strophe ,
$iq = converse _api . env . $iq ,
$build = converse _api . env . $build ,
$msg = converse _api . env . $msg ,
$pres = converse _api . env . $pres ,
b64 _sha1 = converse _api . env . b64 _sha1 ,
utils = converse _api . env . utils ;
// Other necessary globals
var $ = converse _api . env . jQuery ,
_ = converse _api . env . _ ,
moment = converse _api . env . moment ;
// For translations
var _ _ = utils . _ _ . bind ( converse ) ;
var _ _ _ = utils . _ _ _ ;
2016-02-28 10:00:22 +01:00
2016-02-26 18:28:18 +01:00
// Add Strophe Namespaces
Strophe . addNamespace ( 'MUC_ADMIN' , Strophe . NS . MUC + "#admin" ) ;
Strophe . addNamespace ( 'MUC_OWNER' , Strophe . NS . MUC + "#owner" ) ;
Strophe . addNamespace ( 'MUC_REGISTER' , "jabber:iq:register" ) ;
Strophe . addNamespace ( 'MUC_ROOMCONF' , Strophe . NS . MUC + "#roomconfig" ) ;
Strophe . addNamespace ( 'MUC_USER' , Strophe . NS . MUC + "#user" ) ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-muc' , {
/ * O p t i o n a l d e p e n d e n c i e s a r e o t h e r p l u g i n s w h i c h m i g h t b e
* overridden or relied upon , if they exist , otherwise they ' re ignored .
*
* However , if the setting "strict_plugin_dependencies" is set to true ,
* an error will be raised if the plugin is not found .
*
* NB : These plugins need to have already been loaded via require . js .
* /
optional _dependencies : [ "converse-controlbox" ] ,
2016-02-26 18:28:18 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
2016-03-16 12:49:35 +01:00
wrappedChatBox : function ( chatbox ) {
/ * W r a p a c h a t b o x f o r o u t s i d e c o n s u m p t i o n ( i . e . s o t h a t i t c a n b e
* returned via the API .
* /
if ( ! chatbox ) { return ; }
2016-07-26 08:00:30 +02:00
var view = converse . chatboxviews . get ( chatbox . get ( 'id' ) ) ;
2016-09-16 14:35:02 +02:00
var box = this . _ _super _ _ . wrappedChatBox . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
box . is _chatroom = view . is _chatroom ;
return box ;
} ,
2016-02-26 18:28:18 +01:00
Features : {
addClientFeatures : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . addClientFeatures . apply ( this , arguments ) ;
2016-06-20 21:11:43 +02:00
if ( converse . allow _muc _invitations ) {
converse . connection . disco . addFeature ( 'jabber:x:conference' ) ; // Invites
}
if ( converse . allow _muc ) {
converse . connection . disco . addFeature ( Strophe . NS . MUC ) ;
2016-02-26 18:28:18 +01:00
}
}
} ,
ControlBoxView : {
renderContactsPanel : function ( ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
this . _ _super _ _ . renderContactsPanel . apply ( this , arguments ) ;
2016-02-26 18:28:18 +01:00
if ( converse . allow _muc ) {
this . roomspanel = new converse . RoomsPanel ( {
'$parent' : this . $el . find ( '.controlbox-panes' ) ,
'model' : new ( Backbone . Model . extend ( {
id : b64 _sha1 ( 'converse.roomspanel' + converse . bare _jid ) , // Required by sessionStorage
browserStorage : new Backbone . BrowserStorage [ converse . storage ] (
b64 _sha1 ( 'converse.roomspanel' + converse . bare _jid ) )
} ) ) ( )
} ) ;
this . roomspanel . render ( ) . model . fetch ( ) ;
if ( ! this . roomspanel . model . get ( 'nick' ) ) {
this . roomspanel . model . save ( {
nick : Strophe . getNodeFromJid ( converse . bare _jid )
} ) ;
}
}
} ,
onConnected : function ( ) {
2016-03-29 08:56:13 +02:00
// TODO: This can probably be refactored to be an event
// handler (and therefore removed from overrides)
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
this . _ _super _ _ . onConnected . apply ( this , arguments ) ;
2016-02-26 18:28:18 +01:00
if ( this . model . get ( 'connected' ) ) {
converse . features . off ( 'add' , this . featureAdded , this ) ;
converse . features . on ( 'add' , this . featureAdded , this ) ;
// Features could have been added before the controlbox was
// initialized. We're only interested in MUC
var feature = converse . features . findWhere ( {
'var' : Strophe . NS . MUC
} ) ;
if ( feature ) {
this . featureAdded ( feature ) ;
}
}
} ,
featureAdded : function ( feature ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
2016-02-26 18:28:18 +01:00
if ( ( feature . get ( 'var' ) === Strophe . NS . MUC ) && ( converse . allow _muc ) ) {
this . roomspanel . model . save ( { muc _domain : feature . get ( 'from' ) } ) ;
var $server = this . $el . find ( 'input.new-chatroom-server' ) ;
if ( ! $server . is ( ':focus' ) ) {
$server . val ( this . roomspanel . model . get ( 'muc_domain' ) ) ;
}
}
}
} ,
2016-03-07 18:54:07 +01:00
ChatBoxViews : {
onChatBoxAdded : function ( item ) {
var view = this . get ( item . get ( 'id' ) ) ;
2016-03-29 08:56:13 +02:00
if ( ! view && item . get ( 'type' ) === 'chatroom' ) {
2016-03-07 18:54:07 +01:00
view = new converse . ChatRoomView ( { 'model' : item } ) ;
2016-03-29 08:56:13 +02:00
return this . add ( item . get ( 'id' ) , view ) ;
2016-03-07 18:54:07 +01:00
} else {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . onChatBoxAdded . apply ( this , arguments ) ;
2016-03-07 18:54:07 +01:00
}
}
}
2016-02-26 18:28:18 +01:00
} ,
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
var converse = this . converse ;
// Configuration values for this plugin
2016-08-12 22:52:21 +02:00
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
2016-03-16 12:49:35 +01:00
this . updateSettings ( {
2016-02-26 18:28:18 +01:00
allow _muc : true ,
2016-08-12 22:52:21 +02:00
allow _muc _invitations : true ,
auto _join _on _invite : false ,
auto _join _rooms : [ ] ,
2016-03-19 14:33:31 +01:00
auto _list _rooms : false ,
2016-02-26 18:28:18 +01:00
hide _muc _server : false ,
2016-08-12 22:52:21 +02:00
muc _history _max _stanzas : undefined ,
muc _instant _rooms : true ,
muc _nickname _from _jid : false ,
2016-03-16 12:49:35 +01:00
show _toolbar : true ,
} ) ;
2016-02-26 18:28:18 +01:00
converse . ChatRoomView = converse . ChatBoxView . extend ( {
/ * B a c k b o n e V i e w w h i c h r e n d e r s a c h a t r o o m , b a s e d u p o n t h e v i e w
2016-03-29 08:56:13 +02:00
* for normal one - on - one chat boxes .
* /
2016-02-26 18:28:18 +01:00
length : 300 ,
tagName : 'div' ,
className : 'chatbox chatroom' ,
2016-03-29 08:56:13 +02:00
is _chatroom : true ,
2016-02-26 18:28:18 +01:00
events : {
'click .close-chatbox-button' : 'close' ,
'click .configure-chatroom-button' : 'configureChatRoom' ,
'click .toggle-smiley' : 'toggleEmoticonMenu' ,
'click .toggle-smiley ul li' : 'insertEmoticon' ,
'click .toggle-clear' : 'clearChatRoomMessages' ,
'click .toggle-call' : 'toggleCall' ,
'click .toggle-occupants a' : 'toggleOccupants' ,
2016-06-20 21:11:43 +02:00
'click .new-msgs-indicator' : 'viewUnreadMessages' ,
2016-07-28 18:06:31 +02:00
'click .occupant' : 'onOccupantClicked' ,
2016-06-20 21:11:43 +02:00
'keypress textarea.chat-textarea' : 'keyPressed'
2016-02-26 18:28:18 +01:00
} ,
initialize : function ( ) {
this . model . messages . on ( 'add' , this . onMessageAdded , this ) ;
2016-05-03 17:37:10 +02:00
this . model . on ( 'show' , this . show , this ) ;
this . model . on ( 'destroy' , this . hide , this ) ;
2016-08-12 22:52:21 +02:00
this . model . on ( 'change:chat_state' , this . sendChatState , this ) ;
2016-05-03 17:37:10 +02:00
2016-02-26 18:28:18 +01:00
this . occupantsview = new converse . ChatRoomOccupantsView ( {
model : new converse . ChatRoomOccupants ( { nick : this . model . get ( 'nick' ) } )
} ) ;
var id = b64 _sha1 ( 'converse.occupants' + converse . bare _jid + this . model . get ( 'id' ) + this . model . get ( 'nick' ) ) ;
2016-09-16 14:35:02 +02:00
this . occupantsview . model . browserStorage = new Backbone . BrowserStorage . session ( id ) ;
2016-02-26 18:28:18 +01:00
this . occupantsview . chatroomview = this ;
2016-08-12 22:52:21 +02:00
2016-02-26 18:28:18 +01:00
this . render ( ) . $el . hide ( ) ;
this . occupantsview . model . fetch ( { add : true } ) ;
2016-07-28 18:06:31 +02:00
var nick = this . model . get ( 'nick' ) ;
if ( ! nick ) {
this . checkForReservedNick ( ) ;
} else {
this . join ( nick ) ;
}
2016-06-20 21:11:43 +02:00
this . fetchMessages ( ) . insertIntoDOM ( ) ;
// XXX: adding the event below to the events map above doesn't work.
// The code that gets executed because of that looks like this:
// this.$el.on('scroll', '.chat-content', this.markScrolled.bind(this));
// Which for some reason doesn't work.
// So working around that fact here:
this . $el . find ( '.chat-content' ) . on ( 'scroll' , this . markScrolled . bind ( this ) ) ;
2016-02-26 18:28:18 +01:00
converse . emit ( 'chatRoomOpened' , this ) ;
2016-04-01 15:33:14 +02:00
} ,
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
insertIntoDOM : function ( ) {
var view = converse . chatboxviews . get ( "controlbox" ) ;
if ( view ) {
this . $el . insertAfter ( view . $el ) ;
} else {
$ ( '#conversejs' ) . prepend ( this . $el ) ;
}
return this ;
} ,
2016-02-26 18:28:18 +01:00
render : function ( ) {
this . $el . attr ( 'id' , this . model . get ( 'box_id' ) )
. html ( converse . templates . chatroom ( this . model . toJSON ( ) ) ) ;
this . renderChatArea ( ) ;
2016-03-29 08:56:13 +02:00
window . setTimeout ( converse . refreshWebkit , 50 ) ;
2016-02-26 18:28:18 +01:00
return this ;
} ,
renderChatArea : function ( ) {
if ( ! this . $ ( '.chat-area' ) . length ) {
this . $ ( '.chatroom-body' ) . empty ( )
. append (
converse . templates . chatarea ( {
2016-06-20 21:11:43 +02:00
'unread_msgs' : _ _ ( 'You have unread messages' ) ,
2016-02-26 18:28:18 +01:00
'show_toolbar' : converse . show _toolbar ,
'label_message' : _ _ ( 'Message' )
} ) )
. append ( this . occupantsview . render ( ) . $el ) ;
this . renderToolbar ( ) ;
this . $content = this . $el . find ( '.chat-content' ) ;
}
this . toggleOccupants ( null , true ) ;
return this ;
} ,
2016-03-29 08:56:13 +02:00
close : function ( ev ) {
converse . connection . deleteHandler ( this . handler ) ;
this . leave ( ) ;
converse . ChatBoxView . prototype . close . apply ( this , arguments ) ;
} ,
2016-02-26 18:28:18 +01:00
toggleOccupants : function ( ev , preserve _state ) {
if ( ev ) {
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
}
if ( preserve _state ) {
// Bit of a hack, to make sure that the sidebar's state doesn't change
this . model . set ( { hidden _occupants : ! this . model . get ( 'hidden_occupants' ) } ) ;
}
if ( ! this . model . get ( 'hidden_occupants' ) ) {
this . model . save ( { hidden _occupants : true } ) ;
2016-06-20 21:11:43 +02:00
this . $ ( '.icon-hide-users' ) . removeClass ( 'icon-hide-users' ) . addClass ( 'icon-show-users' ) ;
2016-02-26 18:28:18 +01:00
this . $ ( '.occupants' ) . addClass ( 'hidden' ) ;
this . $ ( '.chat-area' ) . addClass ( 'full' ) ;
this . scrollDown ( ) ;
} else {
this . model . save ( { hidden _occupants : false } ) ;
2016-06-20 21:11:43 +02:00
this . $ ( '.icon-show-users' ) . removeClass ( 'icon-show-users' ) . addClass ( 'icon-hide-users' ) ;
2016-02-26 18:28:18 +01:00
this . $ ( '.chat-area' ) . removeClass ( 'full' ) ;
this . $ ( 'div.occupants' ) . removeClass ( 'hidden' ) ;
this . scrollDown ( ) ;
}
} ,
2016-07-28 18:06:31 +02:00
onOccupantClicked : function ( ev ) {
/ * W h e n a n o c c u p a n t i s c l i c k e d , i n s e r t t h e i r n i c k n a m e i n t o
* the chat textarea input .
* /
this . insertIntoTextArea ( ev . target . textContent ) ;
} ,
2016-03-07 18:54:07 +01:00
directInvite : function ( recipient , reason ) {
2016-02-26 18:28:18 +01:00
var attrs = {
xmlns : 'jabber:x:conference' ,
jid : this . model . get ( 'jid' )
} ;
if ( reason !== null ) { attrs . reason = reason ; }
if ( this . model . get ( 'password' ) ) { attrs . password = this . model . get ( 'password' ) ; }
var invitation = $msg ( {
from : converse . connection . jid ,
2016-03-07 18:54:07 +01:00
to : recipient ,
2016-02-26 18:28:18 +01:00
id : converse . connection . getUniqueId ( )
} ) . c ( 'x' , attrs ) ;
converse . connection . send ( invitation ) ;
2016-03-07 18:54:07 +01:00
converse . emit ( 'roomInviteSent' , {
'room' : this ,
'recipient' : recipient ,
'reason' : reason
} ) ;
2016-02-26 18:28:18 +01:00
} ,
onCommandError : function ( stanza ) {
this . showStatusNotification ( _ _ ( "Error: could not execute the command" ) , true ) ;
} ,
2016-08-12 22:52:21 +02:00
handleChatStateMessage : function ( message ) {
/ * O v e r r i d e t h e m e t h o d o n t h e C h a t B o x V i e w b a s e c l a s s t o
* ignore < gone / > notifications in groupchats .
*
* As laid out in the business rules in XEP - 0085
* http : //xmpp.org/extensions/xep-0085.html#bizrules-groupchat
* /
if ( message . get ( 'fullname' ) === this . model . get ( 'nick' ) ) {
// Don't know about other servers, but OpenFire sends
// back to you your own chat state notifications.
// We ignore them here...
return ;
}
if ( message . get ( 'chat_state' ) !== converse . GONE ) {
converse . ChatBoxView . prototype . handleChatStateMessage . apply ( this , arguments ) ;
}
} ,
sendChatState : function ( ) {
/ * S e n d s a m e s s a g e w i t h t h e s t a t u s o f t h e u s e r i n t h i s c h a t s e s s i o n
* as taken from the 'chat_state' attribute of the chat box .
* See XEP - 0085 Chat State Notifications .
* /
var chat _state = this . model . get ( 'chat_state' ) ;
if ( chat _state === converse . GONE ) {
// <gone/> is not applicable within MUC context
return ;
}
converse . connection . send (
$msg ( { 'to' : this . model . get ( 'jid' ) , 'type' : 'groupchat' } )
. c ( chat _state , { 'xmlns' : Strophe . NS . CHATSTATES } ) . up ( )
. c ( 'no-store' , { 'xmlns' : Strophe . NS . HINTS } ) . up ( )
. c ( 'no-permanent-store' , { 'xmlns' : Strophe . NS . HINTS } )
) ;
} ,
2016-02-26 18:28:18 +01:00
sendChatRoomMessage : function ( text ) {
var msgid = converse . connection . getUniqueId ( ) ;
var msg = $msg ( {
to : this . model . get ( 'jid' ) ,
from : converse . connection . jid ,
type : 'groupchat' ,
id : msgid
} ) . c ( "body" ) . t ( text ) . up ( )
. c ( "x" , { xmlns : "jabber:x:event" } ) . c ( "composing" ) ;
converse . connection . send ( msg ) ;
this . model . messages . create ( {
2016-06-20 21:11:43 +02:00
fullname : this . model . get ( 'nick' ) ,
2016-02-26 18:28:18 +01:00
sender : 'me' ,
time : moment ( ) . format ( ) ,
message : text ,
msgid : msgid
} ) ;
} ,
setAffiliation : function ( room , jid , affiliation , reason , onSuccess , onError ) {
var item = $build ( "item" , { jid : jid , affiliation : affiliation } ) ;
var iq = $iq ( { to : room , type : "set" } ) . c ( "query" , { xmlns : Strophe . NS . MUC _ADMIN } ) . cnode ( item . node ) ;
if ( reason !== null ) { iq . c ( "reason" , reason ) ; }
return converse . connection . sendIQ ( iq . tree ( ) , onSuccess , onError ) ;
} ,
modifyRole : function ( room , nick , role , reason , onSuccess , onError ) {
var item = $build ( "item" , { nick : nick , role : role } ) ;
var iq = $iq ( { to : room , type : "set" } ) . c ( "query" , { xmlns : Strophe . NS . MUC _ADMIN } ) . cnode ( item . node ) ;
if ( reason !== null ) { iq . c ( "reason" , reason ) ; }
return converse . connection . sendIQ ( iq . tree ( ) , onSuccess , onError ) ;
} ,
member : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'member' , reason , handler _cb , error _cb ) ;
} ,
revoke : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'none' , reason , handler _cb , error _cb ) ;
} ,
owner : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'owner' , reason , handler _cb , error _cb ) ;
} ,
admin : function ( room , jid , reason , handler _cb , error _cb ) {
return this . setAffiliation ( room , jid , 'admin' , reason , handler _cb , error _cb ) ;
} ,
validateRoleChangeCommand : function ( command , args ) {
/ * C h e c k t h a t a c o m m a n d t o c h a n g e a c h a t r o o m u s e r ' s r o l e o r
2016-03-29 08:56:13 +02:00
* affiliation has anough arguments .
* /
2016-02-26 18:28:18 +01:00
// TODO check if first argument is valid
if ( args . length < 1 || args . length > 2 ) {
this . showStatusNotification (
_ _ ( "Error: the \"" + command + "\" command takes two arguments, the user's nickname and optionally a reason." ) ,
true
) ;
return false ;
}
return true ;
} ,
2016-03-16 12:49:35 +01:00
clearChatRoomMessages : function ( ev ) {
if ( typeof ev !== "undefined" ) { ev . stopPropagation ( ) ; }
var result = confirm ( _ _ ( "Are you sure you want to clear the messages from this room?" ) ) ;
if ( result === true ) {
this . $content . empty ( ) ;
}
return this ;
} ,
2016-08-12 22:52:21 +02:00
onMessageSubmitted : function ( text ) {
2016-02-26 18:28:18 +01:00
/ * G e t s c a l l e d w h e n t h e u s e r p r e s s e s e n t e r t o s e n d o f f a
2016-03-29 08:56:13 +02:00
* message in a chat room .
*
* Parameters :
* ( String ) text - The message text .
* /
2016-02-26 18:28:18 +01:00
var match = text . replace ( /^\s*/ , "" ) . match ( /^\/(.*?)(?: (.*))?$/ ) || [ false , '' , '' ] ,
args = match [ 2 ] && match [ 2 ] . splitOnce ( ' ' ) || [ ] ;
switch ( match [ 1 ] ) {
case 'admin' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'admin' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'ban' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'outcast' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'clear' :
this . clearChatRoomMessages ( ) ;
break ;
case 'deop' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'occupant' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'help' :
this . showHelpMessages ( [
'<strong>/admin</strong>: ' + _ _ ( "Change user's affiliation to admin" ) ,
'<strong>/ban</strong>: ' + _ _ ( 'Ban user from room' ) ,
'<strong>/clear</strong>: ' + _ _ ( 'Remove messages' ) ,
'<strong>/deop</strong>: ' + _ _ ( 'Change user role to occupant' ) ,
'<strong>/help</strong>: ' + _ _ ( 'Show this menu' ) ,
'<strong>/kick</strong>: ' + _ _ ( 'Kick user from room' ) ,
'<strong>/me</strong>: ' + _ _ ( 'Write in 3rd person' ) ,
'<strong>/member</strong>: ' + _ _ ( 'Grant membership to a user' ) ,
'<strong>/mute</strong>: ' + _ _ ( "Remove user's ability to post messages" ) ,
'<strong>/nick</strong>: ' + _ _ ( 'Change your nickname' ) ,
'<strong>/op</strong>: ' + _ _ ( 'Grant moderator role to user' ) ,
'<strong>/owner</strong>: ' + _ _ ( 'Grant ownership of this room' ) ,
'<strong>/revoke</strong>: ' + _ _ ( "Revoke user's membership" ) ,
'<strong>/topic</strong>: ' + _ _ ( 'Set room topic' ) ,
'<strong>/voice</strong>: ' + _ _ ( 'Allow muted user to post messages' )
] ) ;
break ;
case 'kick' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'none' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'mute' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'visitor' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'member' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'member' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'nick' :
converse . connection . send ( $pres ( {
from : converse . connection . jid ,
to : this . getRoomJIDAndNick ( match [ 2 ] ) ,
id : converse . connection . getUniqueId ( )
} ) . tree ( ) ) ;
break ;
case 'owner' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'owner' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'op' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'moderator' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'revoke' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . setAffiliation (
this . model . get ( 'jid' ) , args [ 0 ] , 'none' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
case 'topic' :
converse . connection . send (
$msg ( {
to : this . model . get ( 'jid' ) ,
from : converse . connection . jid ,
type : "groupchat"
} ) . c ( "subject" , { xmlns : "jabber:client" } ) . t ( match [ 2 ] ) . tree ( )
) ;
break ;
case 'voice' :
if ( ! this . validateRoleChangeCommand ( match [ 1 ] , args ) ) { break ; }
this . modifyRole (
this . model . get ( 'jid' ) , args [ 0 ] , 'occupant' , args [ 1 ] ,
undefined , this . onCommandError . bind ( this ) ) ;
break ;
default :
this . sendChatRoomMessage ( text ) ;
break ;
}
} ,
handleMUCStanza : function ( stanza ) {
var xmlns , xquery , i ;
var from = stanza . getAttribute ( 'from' ) ;
var is _mam = $ ( stanza ) . find ( '[xmlns="' + Strophe . NS . MAM + '"]' ) . length > 0 ;
if ( ! from || ( this . model . get ( 'id' ) !== from . split ( "/" ) [ 0 ] ) || is _mam ) {
return true ;
}
if ( stanza . nodeName === "message" ) {
_ . compose ( this . onChatRoomMessage . bind ( this ) , this . showStatusMessages . bind ( this ) ) ( stanza ) ;
} else if ( stanza . nodeName === "presence" ) {
xquery = stanza . getElementsByTagName ( "x" ) ;
if ( xquery . length > 0 ) {
for ( i = 0 ; i < xquery . length ; i ++ ) {
xmlns = xquery [ i ] . getAttribute ( "xmlns" ) ;
if ( xmlns && xmlns . match ( Strophe . NS . MUC ) ) {
this . onChatRoomPresence ( stanza ) ;
break ;
}
}
}
}
return true ;
} ,
getRoomJIDAndNick : function ( nick ) {
2016-07-28 18:06:31 +02:00
if ( nick ) {
this . model . save ( { 'nick' : nick } ) ;
} else {
nick = this . model . get ( 'nick' ) ;
}
2016-02-26 18:28:18 +01:00
var room = this . model . get ( 'jid' ) ;
var node = Strophe . getNodeFromJid ( room ) ;
var domain = Strophe . getDomainFromJid ( room ) ;
return node + "@" + domain + ( nick !== null ? "/" + nick : "" ) ;
} ,
2016-07-28 18:06:31 +02:00
join : function ( nick , password ) {
2016-02-26 18:28:18 +01:00
var stanza = $pres ( {
2016-07-28 18:06:31 +02:00
'from' : converse . connection . jid ,
'to' : this . getRoomJIDAndNick ( nick )
} ) . c ( "x" , { 'xmlns' : Strophe . NS . MUC } )
. c ( "history" , { 'maxstanzas' : converse . muc _history _max _stanzas } ) . up ( ) ;
2016-02-26 18:28:18 +01:00
if ( password ) {
stanza . cnode ( Strophe . xmlElement ( "password" , [ ] , password ) ) ;
}
if ( ! this . handler ) {
this . handler = converse . connection . addHandler ( this . handleMUCStanza . bind ( this ) ) ;
}
this . model . set ( 'connection_status' , Strophe . Status . CONNECTING ) ;
return converse . connection . send ( stanza ) ;
} ,
leave : function ( exit _msg ) {
var presenceid = converse . connection . getUniqueId ( ) ;
var presence = $pres ( {
type : "unavailable" ,
id : presenceid ,
from : converse . connection . jid ,
to : this . getRoomJIDAndNick ( )
} ) ;
if ( exit _msg !== null ) {
presence . c ( "status" , exit _msg ) ;
}
converse . connection . addHandler (
2016-03-29 08:56:13 +02:00
function ( ) {
this . model . set ( 'connection_status' , Strophe . Status . DISCONNECTED ) ;
} . bind ( this ) ,
2016-02-26 18:28:18 +01:00
null , "presence" , null , presenceid ) ;
converse . connection . send ( presence ) ;
} ,
renderConfigurationForm : function ( stanza ) {
var $form = this . $el . find ( 'form.chatroom-form' ) ,
$fieldset = $form . children ( 'fieldset:first' ) ,
$stanza = $ ( stanza ) ,
$fields = $stanza . find ( 'field' ) ,
title = $stanza . find ( 'title' ) . text ( ) ,
instructions = $stanza . find ( 'instructions' ) . text ( ) ;
$fieldset . find ( 'span.spinner' ) . remove ( ) ;
$fieldset . append ( $ ( '<legend>' ) . text ( title ) ) ;
if ( instructions && instructions !== title ) {
$fieldset . append ( $ ( '<p class="instructions">' ) . text ( instructions ) ) ;
}
_ . each ( $fields , function ( field ) {
$fieldset . append ( utils . xForm2webForm ( $ ( field ) , $stanza ) ) ;
} ) ;
$form . append ( '<fieldset></fieldset>' ) ;
$fieldset = $form . children ( 'fieldset:last' ) ;
$fieldset . append ( '<input type="submit" class="pure-button button-primary" value="' + _ _ ( 'Save' ) + '"/>' ) ;
$fieldset . append ( '<input type="button" class="pure-button button-cancel" value="' + _ _ ( 'Cancel' ) + '"/>' ) ;
$fieldset . find ( 'input[type=button]' ) . on ( 'click' , this . cancelConfiguration . bind ( this ) ) ;
$form . on ( 'submit' , this . saveConfiguration . bind ( this ) ) ;
} ,
sendConfiguration : function ( config , onSuccess , onError ) {
// Send an IQ stanza with the room configuration.
var iq = $iq ( { to : this . model . get ( 'jid' ) , type : "set" } )
. c ( "query" , { xmlns : Strophe . NS . MUC _OWNER } )
. c ( "x" , { xmlns : Strophe . NS . XFORM , type : "submit" } ) ;
_ . each ( config , function ( node ) { iq . cnode ( node ) . up ( ) ; } ) ;
return converse . connection . sendIQ ( iq . tree ( ) , onSuccess , onError ) ;
} ,
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 ( ) {
configArray . push ( utils . webForm2xForm ( this ) ) ;
if ( ! -- count ) {
that . sendConfiguration (
configArray ,
that . onConfigSaved . bind ( that ) ,
that . onErrorConfigSaved . bind ( that )
) ;
}
} ) ;
this . $el . find ( 'div.chatroom-form-container' ) . hide (
function ( ) {
$ ( this ) . remove ( ) ;
that . $el . find ( '.chat-area' ) . removeClass ( 'hidden' ) ;
that . $el . find ( '.occupants' ) . removeClass ( 'hidden' ) ;
} ) ;
} ,
onConfigSaved : function ( stanza ) {
// TODO: provide feedback
} ,
onErrorConfigSaved : function ( stanza ) {
this . showStatusNotification ( _ _ ( "An error occurred while trying to save the form." ) ) ;
} ,
cancelConfiguration : function ( ev ) {
ev . preventDefault ( ) ;
var that = this ;
this . $el . find ( 'div.chatroom-form-container' ) . hide (
function ( ) {
$ ( this ) . remove ( ) ;
that . $el . find ( '.chat-area' ) . removeClass ( 'hidden' ) ;
that . $el . find ( '.occupants' ) . removeClass ( 'hidden' ) ;
} ) ;
} ,
configureChatRoom : function ( ev ) {
2016-08-12 22:52:21 +02:00
if ( typeof ev !== 'undefined' && ev . preventDefault ) {
ev . preventDefault ( ) ;
}
2016-02-26 18:28:18 +01:00
if ( this . $el . find ( 'div.chatroom-form-container' ) . length ) {
return ;
}
this . $ ( '.chatroom-body' ) . children ( ) . addClass ( 'hidden' ) ;
this . $ ( '.chatroom-body' ) . append ( converse . templates . chatroom _form ( ) ) ;
converse . connection . sendIQ (
$iq ( {
to : this . model . get ( 'jid' ) ,
type : "get"
} ) . c ( "query" , { xmlns : Strophe . NS . MUC _OWNER } ) . tree ( ) ,
this . renderConfigurationForm . bind ( this )
) ;
} ,
2016-07-28 18:06:31 +02:00
submitNickname : function ( ev ) {
ev . preventDefault ( ) ;
var $nick = this . $el . find ( 'input[name=nick]' ) ;
var nick = $nick . val ( ) ;
if ( ! nick ) {
$nick . addClass ( 'error' ) ;
return ;
}
else {
$nick . removeClass ( 'error' ) ;
}
this . $el . find ( '.chatroom-form-container' ) . replaceWith ( '<span class="spinner centered"/>' ) ;
this . join ( nick ) ;
} ,
checkForReservedNick : function ( ) {
/ * U s e r s e r v i c e - d i s c o v e r y t o a s t h e X M P P s e r v e r w h e t h e r
* this user has a reserved nickname for this room .
* If so , we ' ll use that , otherwise we render the nickname
* form .
* /
this . showSpinner ( ) ;
converse . connection . sendIQ (
$iq ( {
'to' : this . model . get ( 'jid' ) ,
'from' : converse . connection . jid ,
'type' : "get"
} ) . c ( "query" , {
'xmlns' : Strophe . NS . DISCO _INFO ,
'node' : 'x-roomuser-item'
} ) ,
this . onNickNameFound . bind ( this ) ,
2016-08-12 22:52:21 +02:00
this . onNickNameNotFound . bind ( this )
2016-07-28 18:06:31 +02:00
) ;
} ,
onNickNameFound : function ( iq ) {
/ * W e ' v e r e c e i v e d a n I Q r e s p o n s e f r o m t h e s e r v e r w h i c h
* might contain the user ' s reserved nickname .
* If no nickname is found , we render a form for them to
* specify one .
* /
var nick = $ ( iq )
. find ( 'query[node="x-roomuser-item"] identity' )
. attr ( 'name' ) ;
if ( ! nick ) {
2016-08-12 22:52:21 +02:00
this . onNickNameNotFound ( ) ;
2016-07-28 18:06:31 +02:00
} else {
this . join ( nick ) ;
}
} ,
2016-08-12 22:52:21 +02:00
onNickNameNotFound : function ( message ) {
if ( converse . muc _nickname _from _jid ) {
// We try to enter the room with the node part of
// the user's JID.
this . join ( Strophe . unescapeNode ( Strophe . getNodeFromJid ( converse . bare _jid ) ) ) ;
} else {
this . renderNicknameForm ( message ) ;
}
} ,
getDefaultNickName : function ( ) {
/ * T h e d e f a u l t n i c k n a m e ( u s e d w h e n m u c _ n i c k n a m e _ f r o m _ j i d i s t r u e )
* is the node part of the user ' s JID .
* We put this in a separate method so that it can be
* overridden by plugins .
* /
return Strophe . unescapeNode ( Strophe . getNodeFromJid ( converse . bare _jid ) ) ;
} ,
onNicknameClash : function ( presence ) {
/ * W h e n t h e n i c k n a m e i s a l r e a d y t a k e n , w e e i t h e r r e n d e r a
* form for the user to choose a new nickname , or we
* try to make the nickname unique by adding an integer to
* it . So john will become john - 2 , and then john - 3 and so on .
*
* Which option is take depends on the value of
* muc _nickname _from _jid .
* /
if ( converse . muc _nickname _from _jid ) {
var nick = presence . getAttribute ( 'from' ) . split ( '/' ) [ 1 ] ;
if ( nick === this . getDefaultNickName ( ) ) {
this . join ( nick + '-2' ) ;
} else {
var del = nick . lastIndexOf ( "-" ) ;
var num = nick . substring ( del + 1 , nick . length ) ;
this . join ( nick . substring ( 0 , del + 1 ) + String ( Number ( num ) + 1 ) ) ;
}
} else {
this . renderNicknameForm (
_ _ ( "The nickname you chose is reserved or currently in use, please choose a different one." )
) ;
}
} ,
2016-07-28 18:06:31 +02:00
renderNicknameForm : function ( message ) {
2016-08-12 22:52:21 +02:00
/ * R e n d e r a f o r m w h i c h a l l o w s t h e u s e r t o c h o o s e t h e i r
* nickname .
* /
2016-07-28 18:06:31 +02:00
this . $ ( '.chatroom-body' ) . children ( ) . addClass ( 'hidden' ) ;
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
if ( typeof message !== "string" ) {
message = '' ;
}
this . $ ( '.chatroom-body' ) . append (
converse . templates . chatroom _nickname _form ( {
heading : _ _ ( 'Please choose your nickname' ) ,
label _nickname : _ _ ( 'Nickname' ) ,
label _join : _ _ ( 'Enter room' ) ,
validation _message : message
} ) ) ;
this . $ ( '.chatroom-form' ) . on ( 'submit' , this . submitNickname . bind ( this ) ) ;
} ,
2016-02-26 18:28:18 +01:00
submitPassword : function ( ev ) {
ev . preventDefault ( ) ;
var password = this . $el . find ( '.chatroom-form' ) . find ( 'input[type=password]' ) . val ( ) ;
this . $el . find ( '.chatroom-form-container' ) . replaceWith ( '<span class="spinner centered"/>' ) ;
2016-07-28 18:06:31 +02:00
this . join ( this . model . get ( 'nick' ) , password ) ;
2016-02-26 18:28:18 +01:00
} ,
renderPasswordForm : function ( ) {
this . $ ( '.chatroom-body' ) . children ( ) . hide ( ) ;
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
this . $ ( '.chatroom-body' ) . append (
converse . templates . chatroom _password _form ( {
heading : _ _ ( 'This chatroom requires a password' ) ,
label _password : _ _ ( 'Password: ' ) ,
label _submit : _ _ ( 'Submit' )
} ) ) ;
this . $ ( '.chatroom-form' ) . on ( 'submit' , this . submitPassword . bind ( this ) ) ;
} ,
showDisconnectMessage : function ( msg ) {
this . $ ( '.chat-area' ) . hide ( ) ;
this . $ ( '.occupants' ) . hide ( ) ;
this . $ ( 'span.centered.spinner' ) . remove ( ) ;
this . $ ( '.chatroom-body' ) . append ( $ ( '<p>' + msg + '</p>' ) ) ;
} ,
/* http:/ / xmpp . org / extensions / xep - 0045. html
2016-03-29 08:56:13 +02:00
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* 100 message Entering a room Inform user that any occupant is allowed to see the user ' s full JID
* 101 message ( out of band ) Affiliation change Inform user that his or her affiliation changed while not in the room
* 102 message Configuration change Inform occupants that room now shows unavailable members
* 103 message Configuration change Inform occupants that room now does not show unavailable members
* 104 message Configuration change Inform occupants that a non - privacy - related room configuration change has occurred
* 110 presence Any room presence Inform user that presence refers to one of its own room occupants
* 170 message or initial presence Configuration change Inform occupants that room logging is now enabled
* 171 message Configuration change Inform occupants that room logging is now disabled
* 172 message Configuration change Inform occupants that the room is now non - anonymous
* 173 message Configuration change Inform occupants that the room is now semi - anonymous
* 174 message Configuration change Inform occupants that the room is now fully - anonymous
* 201 presence Entering a room Inform user that a new room has been created
* 210 presence Entering a room Inform user that the service has assigned or modified the occupant ' s roomnick
* 301 presence Removal from room Inform user that he or she has been banned from the room
* 303 presence Exiting a room Inform all occupants of new room nickname
* 307 presence Removal from room Inform user that he or she has been kicked from the room
* 321 presence Removal from room Inform user that he or she is being removed from the room because of an affiliation change
* 322 presence Removal from room Inform user that he or she is being removed from the room because the room has been changed to members - only and the user is not a member
* 332 presence Removal from room Inform user that he or she is being removed from the room because of a system shutdown
* /
2016-02-26 18:28:18 +01:00
infoMessages : {
100 : _ _ ( 'This room is not anonymous' ) ,
102 : _ _ ( 'This room now shows unavailable members' ) ,
103 : _ _ ( 'This room does not show unavailable members' ) ,
104 : _ _ ( 'Non-privacy-related room configuration has changed' ) ,
170 : _ _ ( 'Room logging is now enabled' ) ,
171 : _ _ ( 'Room logging is now disabled' ) ,
172 : _ _ ( 'This room is now non-anonymous' ) ,
173 : _ _ ( 'This room is now semi-anonymous' ) ,
174 : _ _ ( 'This room is now fully-anonymous' ) ,
201 : _ _ ( 'A new room has been created' )
} ,
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." )
} ,
actionInfoMessages : {
/ * 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
2016-03-29 08:56:13 +02:00
* 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 .
* /
2016-02-26 18:28:18 +01:00
301 : _ _ _ ( "<strong>%1$s</strong> has been banned" ) ,
303 : _ _ _ ( "<strong>%1$s</strong>'s nickname has changed" ) ,
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" )
} ,
newNicknameMessages : {
2016-07-28 18:06:31 +02:00
210 : _ _ _ ( 'Your nickname has been automatically set to: <strong>%1$s</strong>' ) ,
2016-02-26 18:28:18 +01:00
303 : _ _ _ ( 'Your nickname has been changed to: <strong>%1$s</strong>' )
} ,
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 .
2016-03-29 08:56:13 +02:00
* Allow user to configure chat room if they are the owner .
* See : http : //xmpp.org/registrar/mucstatus.html
* /
2016-08-12 22:52:21 +02:00
var $el = $ ( el ) , i , disconnect _msgs = [ ] , msgs = [ ] , reasons = [ ] ;
2016-02-26 18:28:18 +01:00
$el . find ( 'x[xmlns="' + Strophe . NS . MUC _USER + '"]' ) . each ( function ( idx , x ) {
var $item = $ ( x ) . find ( 'item' ) ;
if ( Strophe . getBareJidFromJid ( $item . attr ( 'jid' ) ) === converse . bare _jid && $item . attr ( 'affiliation' ) === 'owner' ) {
this . $el . find ( 'a.configure-chatroom-button' ) . show ( ) ;
}
$ ( x ) . find ( 'item reason' ) . each ( function ( idx , reason ) {
if ( $ ( reason ) . text ( ) ) {
reasons . push ( $ ( reason ) . text ( ) ) ;
}
} ) ;
$ ( x ) . find ( 'status' ) . each ( function ( idx , stat ) {
var code = stat . getAttribute ( 'code' ) ;
var from _nick = Strophe . unescapeNode ( Strophe . getResourceFromJid ( $el . attr ( 'from' ) ) ) ;
if ( is _self && code === "210" ) {
msgs . push ( _ _ ( this . newNicknameMessages [ code ] , from _nick ) ) ;
} else if ( is _self && code === "303" ) {
msgs . push ( _ _ ( this . newNicknameMessages [ code ] , $item . attr ( 'nick' ) ) ) ;
} else if ( is _self && _ . contains ( _ . keys ( this . disconnectMessages ) , code ) ) {
disconnect _msgs . push ( this . disconnectMessages [ code ] ) ;
} else if ( ! is _self && _ . contains ( _ . keys ( this . actionInfoMessages ) , code ) ) {
msgs . push ( _ _ ( this . actionInfoMessages [ code ] , from _nick ) ) ;
} else if ( _ . contains ( _ . keys ( this . infoMessages ) , code ) ) {
msgs . push ( this . infoMessages [ code ] ) ;
} else if ( code !== '110' ) {
if ( $ ( stat ) . text ( ) ) {
msgs . push ( $ ( stat ) . text ( ) ) ; // Sometimes the status contains human readable text and not a code.
}
}
} . bind ( this ) ) ;
} . bind ( this ) ) ;
if ( disconnect _msgs . length > 0 ) {
for ( i = 0 ; i < disconnect _msgs . length ; i ++ ) {
this . showDisconnectMessage ( disconnect _msgs [ i ] ) ;
}
for ( i = 0 ; i < reasons . length ; i ++ ) {
this . showDisconnectMessage ( _ _ ( 'The reason given is: "' + reasons [ i ] + '"' ) , true ) ;
}
this . model . set ( 'connection_status' , Strophe . Status . DISCONNECTED ) ;
return ;
}
for ( i = 0 ; i < msgs . length ; i ++ ) {
this . $content . append ( converse . templates . info ( { message : msgs [ i ] } ) ) ;
}
for ( i = 0 ; i < reasons . length ; i ++ ) {
this . showStatusNotification ( _ _ ( 'The reason given is: "' + reasons [ i ] + '"' ) , true ) ;
}
2016-06-20 21:11:43 +02:00
if ( disconnect _msgs . length || msgs . length || reasons . length ) {
this . scrollDown ( ) ;
}
2016-02-26 18:28:18 +01:00
return el ;
} ,
2016-08-12 22:52:21 +02:00
showErrorMessage : function ( presence ) {
2016-02-26 18:28:18 +01:00
// We didn't enter the room, so we must remove it from the MUC
// add-on
2016-08-12 22:52:21 +02:00
var $error = $ ( presence ) . find ( 'error' ) ;
2016-02-26 18:28:18 +01: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 ) {
2016-08-12 22:52:21 +02:00
this . onNicknameClash ( presence ) ;
2016-02-26 18:28:18 +01:00
} else if ( $error . find ( 'item-not-found' ) . length ) {
this . showDisconnectMessage ( _ _ ( "This room does not (yet) exist" ) ) ;
} else if ( $error . find ( 'service-unavailable' ) . length ) {
2016-05-03 17:37:10 +02:00
this . showDisconnectMessage ( _ _ ( "This room has reached its maximum number of occupants" ) ) ;
2016-02-26 18:28:18 +01:00
}
}
} ,
2016-07-28 18:06:31 +02:00
showSpinner : function ( ) {
this . $ ( '.chatroom-body' ) . children ( ) . addClass ( 'hidden' ) ;
this . $el . find ( '.chatroom-body' ) . prepend ( '<span class="spinner centered"/>' ) ;
} ,
hideSpinner : function ( ) {
/ * C h e c k i f t h e s p i n n e r i s b e i n g s h o w n a n d i f s o , h i d e i t .
* Also make sure then that the chat area and occupants
* list are both visible .
* /
var that = this ;
var $spinner = this . $el . find ( '.spinner' ) ;
if ( $spinner . length ) {
$spinner . hide ( function ( ) {
$ ( this ) . remove ( ) ;
that . $el . find ( '.chat-area' ) . removeClass ( 'hidden' ) ;
that . $el . find ( '.occupants' ) . removeClass ( 'hidden' ) ;
2016-09-16 14:35:02 +02:00
that . scrollDown ( ) ;
2016-07-28 18:06:31 +02:00
} ) ;
}
return this ;
} ,
2016-02-26 18:28:18 +01:00
onChatRoomPresence : function ( pres ) {
2016-08-12 22:52:21 +02:00
var $presence = $ ( pres ) , is _self , new _room ;
2016-02-26 18:28:18 +01:00
var nick = this . model . get ( 'nick' ) ;
if ( $presence . attr ( 'type' ) === 'error' ) {
this . model . set ( 'connection_status' , Strophe . Status . DISCONNECTED ) ;
2016-08-12 22:52:21 +02:00
this . showErrorMessage ( pres ) ;
2016-02-26 18:28:18 +01:00
} else {
is _self = ( $presence . find ( "status[code='110']" ) . length ) ||
( $presence . attr ( 'from' ) === this . model . get ( 'id' ) + '/' + Strophe . escapeNode ( nick ) ) ;
2016-08-12 22:52:21 +02:00
new _room = $presence . find ( "status[code='201']" ) . length ;
if ( is _self ) {
2016-02-26 18:28:18 +01:00
this . model . set ( 'connection_status' , Strophe . Status . CONNECTED ) ;
2016-08-12 22:52:21 +02:00
if ( ! converse . muc _instant _rooms && new _room ) {
this . configureChatRoom ( ) ;
} else {
this . hideSpinner ( ) . showStatusMessages ( pres , is _self ) ;
}
2016-02-26 18:28:18 +01:00
}
}
this . occupantsview . updateOccupantsOnPresence ( pres ) ;
} ,
2016-06-20 21:11:43 +02:00
setChatRoomSubject : function ( sender , 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!
this . $content . append (
converse . templates . info ( {
'message' : _ _ ( 'Topic set by %1$s to: %2$s' , sender , subject )
} ) ) ;
this . scrollDown ( ) ;
} ,
2016-02-26 18:28:18 +01:00
onChatRoomMessage : function ( message ) {
var $message = $ ( message ) ,
$forwarded = $message . find ( 'forwarded' ) ,
$delay ;
if ( $forwarded . length ) {
$message = $forwarded . children ( 'message' ) ;
$delay = $forwarded . children ( 'delay' ) ;
}
2016-03-08 13:16:04 +01:00
var jid = $message . attr ( 'from' ) ,
2016-02-26 18:28:18 +01:00
msgid = $message . attr ( 'id' ) ,
resource = Strophe . getResourceFromJid ( jid ) ,
sender = resource && Strophe . unescapeNode ( resource ) || '' ,
2016-06-20 21:11:43 +02:00
subject = $message . children ( 'subject' ) . text ( ) ,
dupes = msgid && this . model . messages . filter ( function ( msg ) {
// Find duplicates.
// Some bots (like HAL in the prosody chatroom)
// respond to commands with the same ID as the
// original message. So we also check the sender.
return msg . get ( 'msgid' ) === msgid && msg . get ( 'fullname' ) === sender ;
} ) ;
if ( dupes && dupes . length ) {
return true ;
2016-02-26 18:28:18 +01:00
}
if ( subject ) {
2016-06-20 21:11:43 +02:00
this . setChatRoomSubject ( sender , subject ) ;
2016-02-26 18:28:18 +01:00
}
if ( sender === '' ) {
return true ;
}
2016-06-20 21:11:43 +02:00
this . model . createMessage ( $message , $delay , message ) ;
2016-02-26 18:28:18 +01:00
if ( sender !== this . model . get ( 'nick' ) ) {
// We only emit an event if it's not our own message
converse . emit ( 'message' , message ) ;
}
return true ;
} ,
2016-03-16 12:49:35 +01:00
2016-02-26 18:28:18 +01:00
fetchArchivedMessages : function ( options ) {
/ * F e t c h a r c h i v e d c h a t m e s s a g e s f r o m t h e X M P P s e r v e r .
2016-03-29 08:56:13 +02:00
*
* Then , upon receiving them , call onChatRoomMessage
* so that they are displayed inside it .
* /
2016-02-26 18:28:18 +01:00
if ( ! converse . features . findWhere ( { 'var' : Strophe . NS . MAM } ) ) {
converse . log ( "Attempted to fetch archived messages but this user's server doesn't support XEP-0313" ) ;
return ;
}
this . addSpinner ( ) ;
converse _api . archive . query ( _ . extend ( options , { 'groupchat' : true } ) ,
function ( messages ) {
this . clearSpinner ( ) ;
if ( messages . length ) {
_ . map ( messages , this . onChatRoomMessage . bind ( this ) ) ;
}
} . bind ( this ) ,
function ( ) {
this . clearSpinner ( ) ;
converse . log ( "Error while trying to fetch archived messages" , "error" ) ;
} . bind ( this )
) ;
}
} ) ;
2016-07-28 18:06:31 +02:00
converse . ChatRoomOccupant = Backbone . Model . extend ( {
initialize : function ( attributes ) {
this . set ( _ . extend ( {
'id' : converse . connection . getUniqueId ( ) ,
} , attributes ) ) ;
}
} ) ;
2016-02-26 18:28:18 +01:00
converse . ChatRoomOccupantView = Backbone . View . extend ( {
tagName : 'li' ,
initialize : function ( ) {
this . model . on ( 'change' , this . render , this ) ;
this . model . on ( 'destroy' , this . destroy , this ) ;
} ,
2016-07-28 18:06:31 +02:00
2016-02-26 18:28:18 +01:00
render : function ( ) {
2016-07-28 18:06:31 +02:00
var new _el = converse . templates . occupant (
2016-02-26 18:28:18 +01:00
_ . extend (
this . model . toJSON ( ) , {
2016-07-28 18:06:31 +02:00
'hint_occupant' : _ _ ( 'Click to mention this user in your message.' ) ,
'desc_moderator' : _ _ ( 'This user is a moderator.' ) ,
'desc_occupant' : _ _ ( 'This user can send messages in this room.' ) ,
'desc_visitor' : _ _ ( 'This user can NOT send messages in this room.' )
2016-02-26 18:28:18 +01:00
} )
) ;
2016-07-28 18:06:31 +02:00
var $parents = this . $el . parents ( ) ;
if ( $parents . length ) {
this . $el . replaceWith ( new _el ) ;
this . setElement ( $parents . first ( ) . children ( '#' + this . model . get ( 'id' ) ) , true ) ;
this . delegateEvents ( ) ;
} else {
this . $el . replaceWith ( new _el ) ;
this . setElement ( new _el , true ) ;
}
2016-02-26 18:28:18 +01:00
return this ;
} ,
destroy : function ( ) {
this . $el . remove ( ) ;
}
} ) ;
converse . ChatRoomOccupants = Backbone . Collection . extend ( {
model : converse . ChatRoomOccupant
} ) ;
converse . ChatRoomOccupantsView = Backbone . Overview . extend ( {
tagName : 'div' ,
className : 'occupants' ,
initialize : function ( ) {
this . model . on ( "add" , this . onOccupantAdded , this ) ;
} ,
render : function ( ) {
this . $el . html (
converse . templates . chatroom _sidebar ( {
2016-06-20 21:11:43 +02:00
'allow_muc_invitations' : converse . allow _muc _invitations ,
2016-05-03 17:37:10 +02:00
'label_invitation' : _ _ ( 'Invite' ) ,
2016-02-26 18:28:18 +01:00
'label_occupants' : _ _ ( 'Occupants' )
} )
) ;
2016-06-20 21:11:43 +02:00
if ( converse . allow _muc _invitations ) {
return this . initInviteWidget ( ) ;
}
return this ;
2016-02-26 18:28:18 +01:00
} ,
onOccupantAdded : function ( item ) {
var view = this . get ( item . get ( 'id' ) ) ;
if ( ! view ) {
view = this . add ( item . get ( 'id' ) , new converse . ChatRoomOccupantView ( { model : item } ) ) ;
} else {
delete view . model ; // Remove ref to old model to help garbage collection
view . model = item ;
view . initialize ( ) ;
}
this . $ ( '.occupant-list' ) . append ( view . render ( ) . $el ) ;
} ,
parsePresence : function ( pres ) {
var id = Strophe . getResourceFromJid ( pres . getAttribute ( "from" ) ) ;
var data = {
nick : id ,
type : pres . getAttribute ( "type" ) ,
states : [ ]
} ;
_ . each ( pres . childNodes , function ( child ) {
switch ( child . nodeName ) {
case "status" :
data . status = child . textContent || null ;
break ;
case "show" :
data . show = child . textContent || null ;
break ;
case "x" :
if ( child . getAttribute ( "xmlns" ) === Strophe . NS . MUC _USER ) {
_ . each ( child . childNodes , function ( item ) {
switch ( item . nodeName ) {
case "item" :
data . affiliation = item . getAttribute ( "affiliation" ) ;
data . role = item . getAttribute ( "role" ) ;
data . jid = item . getAttribute ( "jid" ) ;
data . nick = item . getAttribute ( "nick" ) || data . nick ;
break ;
case "status" :
if ( item . getAttribute ( "code" ) ) {
data . states . push ( item . getAttribute ( "code" ) ) ;
}
}
} ) ;
}
}
} ) ;
return data ;
} ,
2016-07-28 18:06:31 +02:00
findOccupant : function ( data ) {
/ * T r y t o f i n d a n e x i s t i n g o c c u p a n t b a s e d o n t h e p a s s e d i n
* data object .
*
* If we have a JID , we use that as lookup variable ,
* otherwise we use the nick . We don ' t always have both ,
* but should have at least one or the other .
* /
var jid = Strophe . getBareJidFromJid ( data . jid ) ;
if ( jid !== null ) {
return this . model . where ( { 'jid' : jid } ) . pop ( ) ;
} else {
return this . model . where ( { 'nick' : data . nick } ) . pop ( ) ;
}
} ,
2016-02-26 18:28:18 +01:00
updateOccupantsOnPresence : function ( pres ) {
var data = this . parsePresence ( pres ) ;
2016-07-28 18:06:31 +02:00
if ( data . type === 'error' ) {
return true ;
}
var occupant = this . findOccupant ( data ) ;
2016-02-26 18:28:18 +01:00
switch ( data . type ) {
case 'unavailable' :
if ( occupant ) { occupant . destroy ( ) ; }
break ;
default :
2016-07-28 18:06:31 +02:00
var jid = Strophe . getBareJidFromJid ( data . jid ) ;
var attributes = _ . extend ( data , {
'jid' : jid ? jid : undefined ,
'resource' : data . jid ? Strophe . getResourceFromJid ( data . jid ) : undefined
} ) ;
2016-02-26 18:28:18 +01:00
if ( occupant ) {
2016-07-28 18:06:31 +02:00
occupant . save ( attributes ) ;
2016-02-26 18:28:18 +01:00
} else {
2016-07-28 18:06:31 +02:00
this . model . create ( attributes ) ;
2016-02-26 18:28:18 +01:00
}
}
} ,
initInviteWidget : function ( ) {
var $el = this . $ ( 'input.invited-contact' ) ;
$el . typeahead ( {
minLength : 1 ,
highlight : true
} , {
name : 'contacts-dataset' ,
source : function ( q , cb ) {
var results = [ ] ;
_ . each ( converse . roster . filter ( utils . contains ( [ 'fullname' , 'jid' ] , q ) ) , function ( n ) {
results . push ( { value : n . get ( 'fullname' ) , jid : n . get ( 'jid' ) } ) ;
} ) ;
cb ( results ) ;
} ,
templates : {
suggestion : _ . template ( '<p data-jid="{{jid}}">{{value}}</p>' )
}
} ) ;
$el . on ( 'typeahead:selected' , function ( ev , suggestion , dname ) {
var reason = prompt (
_ _ ( _ _ _ ( 'You are about to invite %1$s to the chat room "%2$s". ' ) , suggestion . value , this . model . get ( 'id' ) ) +
_ _ ( "You may optionally include a message, explaining the reason for the invitation." )
) ;
if ( reason !== null ) {
this . chatroomview . directInvite ( suggestion . jid , reason ) ;
}
$ ( ev . target ) . typeahead ( 'val' , '' ) ;
} . bind ( this ) ) ;
return this ;
}
} ) ;
converse . RoomsPanel = Backbone . View . extend ( {
/ * B a c k b o n e V i e w w h i c h r e n d e r s t h e " R o o m s " t a b a n d a c c o m p a n y i n g
2016-03-29 08:56:13 +02:00
* panel in the control box .
*
* In this panel , chat rooms can be listed , joined and new rooms
* can be created .
* /
2016-02-26 18:28:18 +01:00
tagName : 'div' ,
className : 'controlbox-pane' ,
id : 'chatrooms' ,
events : {
'submit form.add-chatroom' : 'createChatRoom' ,
'click input#show-rooms' : 'showRooms' ,
'click a.open-room' : 'createChatRoom' ,
'click a.room-info' : 'showRoomInfo' ,
'change input[name=server]' : 'setDomain' ,
'change input[name=nick]' : 'setNick'
} ,
initialize : function ( cfg ) {
this . $parent = cfg . $parent ;
this . model . on ( 'change:muc_domain' , this . onDomainChange , this ) ;
this . model . on ( 'change:nick' , this . onNickChange , this ) ;
} ,
render : function ( ) {
this . $parent . append (
this . $el . html (
converse . templates . room _panel ( {
'server_input_type' : converse . hide _muc _server && 'hidden' || 'text' ,
'server_label_global_attr' : converse . hide _muc _server && ' hidden' || '' ,
'label_room_name' : _ _ ( 'Room name' ) ,
'label_nickname' : _ _ ( 'Nickname' ) ,
'label_server' : _ _ ( 'Server' ) ,
'label_join' : _ _ ( 'Join Room' ) ,
'label_show_rooms' : _ _ ( 'Show rooms' )
} )
) . hide ( ) ) ;
this . $tabs = this . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
this . $tabs . append ( converse . templates . chatrooms _tab ( { label _rooms : _ _ ( 'Rooms' ) } ) ) ;
return this ;
} ,
onDomainChange : function ( model ) {
var $server = this . $el . find ( 'input.new-chatroom-server' ) ;
$server . val ( model . get ( 'muc_domain' ) ) ;
if ( converse . auto _list _rooms ) {
this . updateRoomsList ( ) ;
}
} ,
onNickChange : function ( model ) {
var $nick = this . $el . find ( 'input.new-chatroom-nick' ) ;
$nick . val ( model . get ( 'nick' ) ) ;
} ,
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 . model . get ( 'muc_domain' ) ) + '</dt>' ) ;
$ ( 'input#show-rooms' ) . show ( ) . siblings ( 'span.spinner' ) . remove ( ) ;
} ,
onRoomsFound : function ( iq ) {
/ * H a n d l e t h e I Q s t a n z a r e t u r n e d f r o m t h e s e r v e r , c o n t a i n i n g
2016-03-29 08:56:13 +02:00
* all its public rooms .
* /
2016-02-26 18:28:18 +01:00
var name , jid , i , fragment ,
$available _chatrooms = this . $el . find ( '#available-chatrooms' ) ;
this . rooms = $ ( iq ) . find ( 'query' ) . find ( 'item' ) ;
if ( this . rooms . length ) {
// For translators: %1$s is a variable and will be
// replaced with the XMPP server name
$available _chatrooms . html ( '<dt>' + _ _ ( 'Rooms on %1$s' , this . model . get ( 'muc_domain' ) ) + '</dt>' ) ;
fragment = document . createDocumentFragment ( ) ;
for ( i = 0 ; i < this . rooms . length ; i ++ ) {
name = Strophe . unescapeNode ( $ ( this . rooms [ i ] ) . attr ( 'name' ) || $ ( this . rooms [ i ] ) . attr ( 'jid' ) ) ;
jid = $ ( this . rooms [ i ] ) . attr ( 'jid' ) ;
fragment . appendChild ( $ (
converse . templates . room _item ( {
'name' : name ,
'jid' : jid ,
'open_title' : _ _ ( 'Click to open this room' ) ,
'info_title' : _ _ ( 'Show more information on this room' )
} )
) [ 0 ] ) ;
}
$available _chatrooms . append ( fragment ) ;
$ ( 'input#show-rooms' ) . show ( ) . siblings ( 'span.spinner' ) . remove ( ) ;
} else {
this . informNoRoomsFound ( ) ;
}
return true ;
} ,
updateRoomsList : function ( ) {
/ * S e n d a n d I Q s t a n z a t o t h e s e r v e r a s k i n g f o r a l l r o o m s
2016-03-29 08:56:13 +02:00
* /
2016-02-26 18:28:18 +01:00
converse . connection . sendIQ (
$iq ( {
to : this . model . get ( 'muc_domain' ) ,
from : converse . connection . jid ,
type : "get"
} ) . c ( "query" , { xmlns : Strophe . NS . DISCO _ITEMS } ) ,
this . onRoomsFound . bind ( this ) ,
this . informNoRoomsFound . bind ( this )
) ;
} ,
showRooms : function ( ) {
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' ) ;
return ;
}
this . $el . find ( 'input.new-chatroom-name' ) . removeClass ( 'error' ) ;
$server . removeClass ( 'error' ) ;
$available _chatrooms . empty ( ) ;
$ ( 'input#show-rooms' ) . hide ( ) . after ( '<span class="spinner"/>' ) ;
this . model . save ( { 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 ,
function ( stanza ) {
var $stanza = $ ( stanza ) ;
// All MUC features found here: http://xmpp.org/registrar/disco-features.html
$dd . find ( 'span.spinner' ) . replaceWith (
converse . templates . room _description ( {
'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 ,
'label_desc' : _ _ ( 'Description:' ) ,
'label_occ' : _ _ ( 'Occupants:' ) ,
'label_features' : _ _ ( 'Features:' ) ,
'label_requires_auth' : _ _ ( 'Requires authentication' ) ,
'label_hidden' : _ _ ( 'Hidden' ) ,
'label_requires_invite' : _ _ ( 'Requires an invitation' ) ,
'label_moderated' : _ _ ( 'Moderated' ) ,
'label_non_anon' : _ _ ( 'Non-anonymous' ) ,
'label_open_room' : _ _ ( 'Open room' ) ,
'label_permanent_room' : _ _ ( 'Permanent room' ) ,
'label_public' : _ _ ( 'Public' ) ,
'label_semi_anon' : _ _ ( 'Semi-anonymous' ) ,
'label_temp_room' : _ _ ( 'Temporary room' ) ,
'label_unmoderated' : _ _ ( 'Unmoderated' )
} ) ) ;
} . bind ( this ) ) ;
}
} ,
createChatRoom : function ( ev ) {
ev . preventDefault ( ) ;
2016-08-12 22:52:21 +02:00
var name , $name , server , $server , jid , chatroom ;
2016-02-26 18:28:18 +01:00
if ( ev . type === 'click' ) {
name = $ ( ev . target ) . text ( ) ;
jid = $ ( ev . target ) . attr ( 'data-room-jid' ) ;
} else {
$name = this . $el . find ( 'input.new-chatroom-name' ) ;
$server = this . $el . find ( 'input.new-chatroom-server' ) ;
server = $server . val ( ) ;
name = $name . val ( ) . trim ( ) ;
$name . val ( '' ) ; // Clear the input
if ( name && server ) {
jid = Strophe . escapeNode ( name . toLowerCase ( ) ) + '@' + server . toLowerCase ( ) ;
$name . removeClass ( 'error' ) ;
$server . removeClass ( 'error' ) ;
this . model . save ( { muc _domain : server } ) ;
} else {
if ( ! name ) { $name . addClass ( 'error' ) ; }
if ( ! server ) { $server . addClass ( 'error' ) ; }
return ;
}
}
chatroom = converse . chatboxviews . showChat ( {
'id' : jid ,
'jid' : jid ,
'name' : name || Strophe . unescapeNode ( Strophe . getNodeFromJid ( jid ) ) ,
2016-03-29 08:56:13 +02:00
'type' : 'chatroom' ,
2016-02-26 18:28:18 +01:00
'box_id' : b64 _sha1 ( jid )
} ) ;
} ,
setDomain : function ( ev ) {
this . model . save ( { muc _domain : ev . target . value } ) ;
} ,
setNick : function ( ev ) {
this . model . save ( { nick : ev . target . value } ) ;
}
} ) ;
2016-03-29 08:56:13 +02:00
/* Support for XEP-0249: Direct MUC invitations */
/* ------------------------------------------------------------ */
converse . onDirectMUCInvitation = function ( message ) {
/* A direct MUC invitation to join a room has been received */
var $message = $ ( message ) ,
$x = $message . children ( 'x[xmlns="jabber:x:conference"]' ) ,
from = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ,
room _jid = $x . attr ( 'jid' ) ,
reason = $x . attr ( 'reason' ) ,
contact = converse . roster . get ( from ) ,
result ;
if ( converse . auto _join _on _invite ) {
result = true ;
} else {
// Invite request might come from someone not your roster list
contact = contact ? contact . get ( 'fullname' ) : Strophe . getNodeFromJid ( from ) ;
if ( ! reason ) {
result = confirm (
_ _ ( _ _ _ ( "%1$s has invited you to join a chat room: %2$s" ) ,
contact , room _jid )
) ;
} else {
result = confirm (
_ _ ( _ _ _ ( '%1$s has invited you to join a chat room: %2$s, and left the following reason: "%3$s"' ) ,
contact , room _jid , reason )
) ;
}
}
if ( result === true ) {
var chatroom = converse . chatboxviews . showChat ( {
'id' : room _jid ,
'jid' : room _jid ,
'name' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( room _jid ) ) ,
'nick' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( converse . connection . jid ) ) ,
'type' : 'chatroom' ,
'box_id' : b64 _sha1 ( room _jid ) ,
'password' : $x . attr ( 'password' )
} ) ;
if ( ! _ . contains (
[ Strophe . Status . CONNECTING , Strophe . Status . CONNECTED ] ,
chatroom . get ( 'connection_status' ) )
) {
2016-07-28 18:06:31 +02:00
converse . chatboxviews . get ( room _jid ) . join ( ) ;
2016-03-29 08:56:13 +02:00
}
}
} ;
2016-05-03 17:37:10 +02:00
var autoJoinRooms = function ( ) {
2016-03-29 08:56:13 +02:00
_ . each ( converse . auto _join _rooms , function ( room ) {
if ( typeof room === 'string' ) {
converse _api . rooms . open ( room ) ;
} else if ( typeof room === 'object' ) {
converse _api . rooms . open ( room . jid , room . nick ) ;
} else {
converse . log ( 'Invalid room criteria specified for "auto_join_rooms"' , 'error' ) ;
}
} ) ;
} ;
2016-05-03 17:37:10 +02:00
converse . on ( 'chatBoxesFetched' , autoJoinRooms ) ;
2016-06-20 21:11:43 +02:00
if ( converse . allow _muc _invitations ) {
var onConnected = function ( ) {
converse . connection . addHandler (
function ( message ) {
converse . onDirectMUCInvitation ( message ) ;
return true ;
} , 'jabber:x:conference' , 'message' ) ;
} ;
converse . on ( 'connected' , onConnected ) ;
converse . on ( 'reconnected' , onConnected ) ;
}
2016-03-29 08:56:13 +02:00
/* ------------------------------------------------------------ */
2016-09-16 14:35:02 +02:00
var _transform = function ( jid , attrs , fetcher ) {
2016-06-20 21:11:43 +02:00
jid = jid . toLowerCase ( ) ;
2016-09-16 14:35:02 +02:00
return converse . wrappedChatBox ( fetcher ( _ . extend ( {
2016-06-20 21:11:43 +02:00
'id' : jid ,
'jid' : jid ,
'name' : Strophe . unescapeNode ( Strophe . getNodeFromJid ( jid ) ) ,
'type' : 'chatroom' ,
'box_id' : b64 _sha1 ( jid )
2016-09-16 14:35:02 +02:00
} , attrs ) ) ) ;
2016-06-20 21:11:43 +02:00
} ;
2016-03-29 08:56:13 +02:00
/ * W e e x t e n d t h e d e f a u l t c o n v e r s e . j s A P I t o a d d m e t h o d s s p e c i f i c t o M U C
* chat rooms .
* /
2016-02-26 18:28:18 +01:00
_ . extend ( converse _api , {
'rooms' : {
2016-06-20 21:11:43 +02:00
'close' : function ( jids ) {
if ( typeof jids === "undefined" ) {
converse . chatboxviews . each ( function ( view ) {
if ( view . is _chatroom && view . model ) {
view . close ( ) ;
}
} ) ;
} else if ( typeof jids === "string" ) {
var view = converse . chatboxviews . get ( jids ) ;
if ( view ) { view . close ( ) ; }
} else {
_ . map ( jids , function ( jid ) {
var view = converse . chatboxviews . get ( jid ) ;
if ( view ) { view . close ( ) ; }
} ) ;
}
} ,
2016-09-16 14:35:02 +02:00
'open' : function ( jids , attrs ) {
if ( typeof attrs === "string" ) {
attrs = { 'nick' : attrs } ;
} else if ( typeof attrs === "undefined" ) {
attrs = { } ;
}
2016-06-20 21:11:43 +02:00
var fetcher = converse . chatboxviews . showChat . bind ( converse . chatboxviews ) ;
2016-02-26 18:28:18 +01:00
if ( typeof jids === "undefined" ) {
throw new TypeError ( 'rooms.open: You need to provide at least one JID' ) ;
} else if ( typeof jids === "string" ) {
2016-09-16 14:35:02 +02:00
return _transform ( jids , attrs , fetcher ) ;
2016-02-26 18:28:18 +01:00
}
2016-09-16 14:35:02 +02:00
return _ . map ( jids , _ . partial ( _transform , _ , attrs , fetcher ) ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-09-16 14:35:02 +02:00
'get' : function ( jids , attrs , create ) {
if ( typeof attrs === "string" ) {
attrs = { 'nick' : attrs } ;
} else if ( typeof attrs === "undefined" ) {
attrs = { } ;
}
2016-07-26 08:00:30 +02:00
if ( typeof jids === "undefined" ) {
var result = [ ] ;
converse . chatboxes . each ( function ( chatbox ) {
if ( chatbox . get ( 'type' ) === 'chatroom' ) {
result . push ( converse . wrappedChatBox ( chatbox ) ) ;
}
} ) ;
return result ;
}
2016-06-20 21:11:43 +02:00
var fetcher = _ . partial ( converse . chatboxviews . getChatBox . bind ( converse . chatboxviews ) , _ , create ) ;
2016-09-16 14:35:02 +02:00
if ( ! attrs . nick ) {
attrs . nick = Strophe . getNodeFromJid ( converse . bare _jid ) ;
2016-06-20 21:11:43 +02:00
}
2016-07-26 08:00:30 +02:00
if ( typeof jids === "string" ) {
2016-09-16 14:35:02 +02:00
return _transform ( jids , attrs , fetcher ) ;
2016-02-26 18:28:18 +01:00
}
2016-09-16 14:35:02 +02:00
return _ . map ( jids , _ . partial ( _transform , _ , attrs , fetcher ) ) ;
2016-02-26 18:28:18 +01:00
}
}
2016-03-16 12:49:35 +01:00
} ) ;
}
} ) ;
} ) ) ;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
2016-04-01 15:33:14 +02:00
// XEP-0059 Result Set Management
2016-03-16 12:49:35 +01:00
( function ( root , factory ) {
2016-04-01 15:33:14 +02:00
define ( "converse-mam" , [
2016-03-16 12:49:35 +01:00
"converse-core" ,
"converse-api" ,
2016-04-01 15:33:14 +02:00
"converse-chatview" , // Could be made a soft dependency
"converse-muc" , // Could be made a soft dependency
"strophe.rsm"
2016-03-16 12:49:35 +01:00
] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
2016-04-01 15:33:14 +02:00
var $ = converse _api . env . jQuery ,
Strophe = converse _api . env . Strophe ,
$iq = converse _api . env . $iq ,
2016-03-16 12:49:35 +01:00
_ = converse _api . env . _ ,
moment = converse _api . env . moment ;
2016-04-01 15:33:14 +02:00
var RSM _ATTRIBUTES = [ 'max' , 'first' , 'last' , 'after' , 'before' , 'index' , 'count' ] ;
// XEP-0313 Message Archive Management
var MAM _ATTRIBUTES = [ 'with' , 'start' , 'end' ] ;
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
Strophe . addNamespace ( 'MAM' , 'urn:xmpp:mam:0' ) ;
Strophe . addNamespace ( 'RSM' , 'http://jabber.org/protocol/rsm' ) ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-mam' , {
2016-03-16 12:49:35 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
Features : {
addClientFeatures : function ( ) {
2016-04-01 15:33:14 +02:00
converse . connection . disco . addFeature ( Strophe . NS . MAM ) ;
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . addClientFeatures . apply ( this , arguments ) ;
2016-03-16 12:49:35 +01:00
}
} ,
2016-06-20 21:11:43 +02:00
ChatBox : {
getMessageAttributes : function ( $message , $delay , original _stanza ) {
2016-09-16 14:35:02 +02:00
var attrs = this . _ _super _ _ . getMessageAttributes . apply ( this , arguments ) ;
2016-06-20 21:11:43 +02:00
attrs . archive _id = $ ( original _stanza ) . find ( 'result[xmlns="' + Strophe . NS . MAM + '"]' ) . attr ( 'id' ) ;
return attrs ;
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
} ,
ChatBoxView : {
render : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . render . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( ! this . disable _mam ) {
this . $content . on ( 'scroll' , _ . debounce ( this . onScroll . bind ( this ) , 100 ) ) ;
}
return result ;
} ,
afterMessagesFetched : function ( ) {
if ( this . disable _mam || ! converse . features . findWhere ( { 'var' : Strophe . NS . MAM } ) ) {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . afterMessagesFetched . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
}
if ( this . model . messages . length < converse . archived _messages _page _size ) {
this . fetchArchivedMessages ( {
'before' : '' , // Page backwards from the most recent message
'with' : this . model . get ( 'jid' ) ,
'max' : converse . archived _messages _page _size
} ) ;
}
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . afterMessagesFetched . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
} ,
fetchArchivedMessages : function ( options ) {
/ * F e t c h a r c h i v e d c h a t m e s s a g e s f r o m t h e X M P P s e r v e r .
*
* Then , upon receiving them , call onMessage on the chat box ,
* so that they are displayed inside it .
* /
if ( ! converse . features . findWhere ( { 'var' : Strophe . NS . MAM } ) ) {
converse . log ( "Attempted to fetch archived messages but this user's server doesn't support XEP-0313" ) ;
return ;
}
if ( this . disable _mam ) {
return ;
}
this . addSpinner ( ) ;
converse . queryForArchivedMessages ( options , function ( messages ) {
this . clearSpinner ( ) ;
if ( messages . length ) {
_ . map ( messages , converse . chatboxes . onMessage . bind ( converse . chatboxes ) ) ;
}
} . bind ( this ) ,
function ( ) {
this . clearSpinner ( ) ;
converse . log ( "Error or timeout while trying to fetch archived messages" , "error" ) ;
} . bind ( this )
) ;
} ,
onScroll : function ( ev ) {
if ( $ ( ev . target ) . scrollTop ( ) === 0 && this . model . messages . length ) {
this . fetchArchivedMessages ( {
'before' : this . model . messages . at ( 0 ) . get ( 'archive_id' ) ,
'with' : this . model . get ( 'jid' ) ,
'max' : converse . archived _messages _page _size
} ) ;
}
} ,
} ,
ChatRoomView : {
render : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . render . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( ! this . disable _mam ) {
this . $content . on ( 'scroll' , _ . debounce ( this . onScroll . bind ( this ) , 100 ) ) ;
}
return result ;
} ,
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
2016-03-16 12:49:35 +01:00
} ,
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
this . updateSettings ( {
2016-04-01 15:33:14 +02:00
archived _messages _page _size : '20' ,
message _archiving : 'never' , // Supported values are 'always', 'never', 'roster' (https://xmpp.org/extensions/xep-0313.html#prefs)
message _archiving _timeout : 8000 , // Time (in milliseconds) to wait before aborting MAM request
2016-03-16 12:49:35 +01:00
} ) ;
2016-04-01 15:33:14 +02:00
converse . queryForArchivedMessages = function ( options , callback , errback ) {
/ * D o a M A M ( X E P - 0 3 1 3 ) q u e r y f o r a r c h i v e d m e s s a g e s .
*
* Parameters :
* ( Object ) options - Query parameters , either MAM - specific or also for Result Set Management .
* ( Function ) callback - A function to call whenever we receive query - relevant stanza .
* ( Function ) errback - A function to call when an error stanza is received .
*
* The options parameter can also be an instance of
* Strophe . RSM to enable easy querying between results pages .
*
* The callback function may be called multiple times , first
* for the initial IQ result and then for each message
* returned . The last time the callback is called , a
* Strophe . RSM object is returned on which "next" or "previous"
* can be called before passing it in again to this method , to
* get the next or previous page in the result set .
* /
var date , messages = [ ] ;
if ( typeof options === "function" ) {
callback = options ;
errback = callback ;
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
if ( ! converse . features . findWhere ( { 'var' : Strophe . NS . MAM } ) ) {
converse . log ( 'This server does not support XEP-0313, Message Archive Management' ) ;
errback ( null ) ;
return ;
}
var queryid = converse . connection . getUniqueId ( ) ;
var attrs = { 'type' : 'set' } ;
if ( typeof options !== "undefined" && options . groupchat ) {
if ( ! options [ 'with' ] ) {
throw new Error ( 'You need to specify a "with" value containing the chat room JID, when querying groupchat messages.' ) ;
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
attrs . to = options [ 'with' ] ;
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
var stanza = $iq ( attrs ) . c ( 'query' , { 'xmlns' : Strophe . NS . MAM , 'queryid' : queryid } ) ;
if ( typeof options !== "undefined" ) {
stanza . c ( 'x' , { 'xmlns' : Strophe . NS . XFORM , 'type' : 'submit' } )
. c ( 'field' , { 'var' : 'FORM_TYPE' , 'type' : 'hidden' } )
. c ( 'value' ) . t ( Strophe . NS . MAM ) . up ( ) . up ( ) ;
if ( options [ 'with' ] && ! options . groupchat ) {
stanza . c ( 'field' , { 'var' : 'with' } ) . c ( 'value' ) . t ( options [ 'with' ] ) . up ( ) . up ( ) ;
}
_ . each ( [ 'start' , 'end' ] , function ( t ) {
if ( options [ t ] ) {
date = moment ( options [ t ] ) ;
if ( date . isValid ( ) ) {
stanza . c ( 'field' , { 'var' : t } ) . c ( 'value' ) . t ( date . format ( ) ) . up ( ) . up ( ) ;
} else {
throw new TypeError ( 'archive.query: invalid date provided for: ' + t ) ;
}
}
} ) ;
stanza . up ( ) ;
if ( options instanceof Strophe . RSM ) {
stanza . cnode ( options . toXML ( ) ) ;
} else if ( _ . intersection ( RSM _ATTRIBUTES , _ . keys ( options ) ) . length ) {
stanza . cnode ( new Strophe . RSM ( options ) . toXML ( ) ) ;
}
2016-03-16 12:49:35 +01:00
}
2016-07-28 18:06:31 +02:00
if ( typeof callback === "function" ) {
converse . connection . addHandler ( function ( message ) {
var $msg = $ ( message ) , rsm ,
$fin = $msg . find ( 'fin[xmlns="' + Strophe . NS . MAM + '"]' ) ;
if ( $fin . length && $fin . attr ( 'queryid' ) === queryid ) {
2016-04-01 15:33:14 +02:00
rsm = new Strophe . RSM ( { xml : $fin . find ( 'set' ) [ 0 ] } ) ;
_ . extend ( rsm , _ . pick ( options , [ 'max' ] ) ) ;
_ . extend ( rsm , _ . pick ( options , MAM _ATTRIBUTES ) ) ;
callback ( messages , rsm ) ;
return false ; // We've received all messages, decommission this handler
} else if ( queryid === $msg . find ( 'result' ) . attr ( 'queryid' ) ) {
messages . push ( message ) ;
}
return true ;
2016-07-28 18:06:31 +02:00
} , Strophe . NS . MAM ) ;
}
2016-04-01 15:33:14 +02:00
converse . connection . sendIQ ( stanza , null , errback , converse . message _archiving _timeout ) ;
2016-03-16 12:49:35 +01:00
} ;
2016-04-01 15:33:14 +02:00
_ . extend ( converse _api , {
/ * E x t e n d d e f a u l t c o n v e r s e . j s A P I t o a d d m e t h o d s s p e c i f i c t o M A M
2016-03-16 12:49:35 +01:00
* /
2016-04-01 15:33:14 +02:00
'archive' : {
'query' : converse . queryForArchivedMessages . bind ( converse )
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
} ) ;
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
converse . onMAMError = function ( iq ) {
if ( $ ( iq ) . find ( 'feature-not-implemented' ) . length ) {
converse . log ( "Message Archive Management (XEP-0313) not supported by this browser" ) ;
} else {
converse . log ( "An error occured while trying to set archiving preferences." ) ;
converse . log ( iq ) ;
}
} ;
converse . onMAMPreferences = function ( feature , iq ) {
/ * H a n d l e r e t u r n e d I Q s t a n z a c o n t a i n i n g M e s s a g e A r c h i v e
* Management ( XEP - 0313 ) preferences .
*
* XXX : For now we only handle the global default preference .
* The XEP also provides for per - JID preferences , which is
* currently not supported in converse . js .
*
* Per JID preferences will be set in chat boxes , so it ' ll
* probbaly be handled elsewhere in any case .
* /
var $prefs = $ ( iq ) . find ( 'prefs[xmlns="' + Strophe . NS . MAM + '"]' ) ;
var default _pref = $prefs . attr ( 'default' ) ;
var stanza ;
if ( default _pref !== converse . message _archiving ) {
stanza = $iq ( { 'type' : 'set' } ) . c ( 'prefs' , { 'xmlns' : Strophe . NS . MAM , 'default' : converse . message _archiving } ) ;
$prefs . children ( ) . each ( function ( idx , child ) {
stanza . cnode ( child ) . up ( ) ;
} ) ;
converse . connection . sendIQ ( stanza , _ . partial ( function ( feature , iq ) {
// XXX: Strictly speaking, the server should respond with the updated prefs
// (see example 18: https://xmpp.org/extensions/xep-0313.html#config)
// but Prosody doesn't do this, so we don't rely on it.
feature . save ( { 'preferences' : { 'default' : converse . message _archiving } } ) ;
} , feature ) ,
converse . onMAMError
) ;
} else {
feature . save ( { 'preferences' : { 'default' : converse . message _archiving } } ) ;
}
} ;
var onFeatureAdded = function ( evt , feature ) {
var prefs = feature . get ( 'preferences' ) || { } ;
if ( feature . get ( 'var' ) === Strophe . NS . MAM && prefs [ 'default' ] !== converse . message _archiving ) {
// Ask the server for archiving preferences
converse . connection . sendIQ (
$iq ( { 'type' : 'get' } ) . c ( 'prefs' , { 'xmlns' : Strophe . NS . MAM } ) ,
_ . partial ( converse . onMAMPreferences , feature ) ,
_ . partial ( converse . onMAMError , feature )
) ;
}
} ;
converse . on ( 'serviceDiscovered' , onFeatureAdded . bind ( converse . features ) ) ;
}
} ) ;
} ) ) ;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
( function ( root , factory ) {
define ( "converse-vcard" , [
"converse-core" ,
"converse-api" ,
"strophe.vcard" ,
] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
var Strophe = converse _api . env . Strophe ,
$ = converse _api . env . jQuery ,
_ = converse _api . env . _ ,
moment = converse _api . env . moment ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-vcard' , {
2016-04-01 15:33:14 +02:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
Features : {
addClientFeatures : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . addClientFeatures . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( converse . use _vcards ) {
converse . connection . disco . addFeature ( Strophe . NS . VCARD ) ;
}
}
} ,
RosterContacts : {
createRequestingContact : function ( presence ) {
var bare _jid = Strophe . getBareJidFromJid ( presence . getAttribute ( 'from' ) ) ;
converse . getVCard (
bare _jid ,
_ . partial ( converse . createRequestingContactFromVCard , presence ) ,
function ( iq , jid ) {
converse . log ( "Error while retrieving vcard for " + jid ) ;
converse . createRequestingContactFromVCard ( presence , iq , jid ) ;
}
) ;
}
}
} ,
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
this . updateSettings ( {
use _vcards : true ,
} ) ;
converse . createRequestingContactFromVCard = function ( presence , iq , jid , fullname , img , img _type , url ) {
var bare _jid = Strophe . getBareJidFromJid ( jid ) ;
var nick = $ ( presence ) . children ( 'nick[xmlns="' + Strophe . NS . NICK + '"]' ) . text ( ) ;
var user _data = {
jid : bare _jid ,
subscription : 'none' ,
ask : null ,
requesting : true ,
fullname : fullname || nick || bare _jid ,
image : img ,
image _type : img _type ,
url : url ,
vcard _updated : moment ( ) . format ( )
} ;
converse . roster . create ( user _data ) ;
converse . emit ( 'contactRequest' , user _data ) ;
} ;
converse . onVCardError = function ( jid , iq , errback ) {
var contact = converse . roster . get ( jid ) ;
if ( contact ) {
contact . save ( { 'vcard_updated' : moment ( ) . format ( ) } ) ;
}
if ( errback ) { errback ( iq , jid ) ; }
} ;
converse . onVCardData = function ( jid , iq , callback ) {
var $vcard = $ ( iq ) . find ( 'vCard' ) ,
fullname = $vcard . find ( 'FN' ) . text ( ) ,
img = $vcard . find ( 'BINVAL' ) . text ( ) ,
img _type = $vcard . find ( 'TYPE' ) . text ( ) ,
url = $vcard . find ( 'URL' ) . text ( ) ;
if ( jid ) {
var contact = converse . roster . get ( jid ) ;
if ( contact ) {
fullname = _ . isEmpty ( fullname ) ? contact . get ( 'fullname' ) || jid : fullname ;
contact . save ( {
'fullname' : fullname ,
'image_type' : img _type ,
'image' : img ,
'url' : url ,
'vcard_updated' : moment ( ) . format ( )
} ) ;
}
}
if ( callback ) {
callback ( iq , jid , fullname , img , img _type , url ) ;
}
} ;
converse . getVCard = function ( jid , callback , errback ) {
/ * R e q u e s t t h e V C a r d o f a n o t h e r u s e r .
*
* Parameters :
* ( String ) jid - The Jabber ID of the user whose VCard
* is being requested .
* ( Function ) callback - A function to call once the VCard is
* returned .
* ( Function ) errback - A function to call if an error occured
* while trying to fetch the VCard .
* /
if ( ! converse . use _vcards ) {
if ( callback ) { callback ( null , jid ) ; }
} else {
converse . connection . vcard . get (
_ . partial ( converse . onVCardData , jid , _ , callback ) ,
jid ,
_ . partial ( converse . onVCardError , jid , _ , errback ) ) ;
}
} ;
var updateVCardForChatBox = function ( evt , chatbox ) {
2016-03-16 12:49:35 +01:00
if ( ! converse . use _vcards ) { return ; }
var jid = chatbox . model . get ( 'jid' ) ,
contact = converse . roster . get ( jid ) ;
if ( ( contact ) && ( ! contact . get ( 'vcard_updated' ) ) ) {
converse . getVCard (
jid ,
function ( iq , jid , fullname , image , image _type , url ) {
chatbox . model . save ( {
'fullname' : fullname || jid ,
'url' : url ,
'image_type' : image _type ,
'image' : image
} ) ;
} ,
function ( ) {
converse . log (
"updateVCardForChatBox: Error occured while fetching vcard"
) ;
}
) ;
}
} ;
converse . on ( 'chatBoxInitialized' , updateVCardForChatBox ) ;
2016-03-19 14:33:31 +01:00
var onContactAdd = function ( contact ) {
if ( ! contact . get ( 'vcard_updated' ) ) {
// This will update the vcard, which triggers a change
// request which will rerender the roster contact.
converse . getVCard ( contact . get ( 'jid' ) ) ;
}
} ;
converse . on ( 'initialized' , function ( ) {
converse . roster . on ( "add" , onContactAdd ) ;
} ) ;
2016-03-16 12:49:35 +01:00
var fetchOwnVCard = function ( ) {
if ( converse . xmppstatus . get ( 'fullname' ) === undefined ) {
converse . getVCard (
null , // No 'to' attr when getting one's own vCard
function ( iq , jid , fullname ) {
converse . xmppstatus . save ( { 'fullname' : fullname } ) ;
}
) ;
}
} ;
converse . on ( 'statusInitialized' , fetchOwnVCard ) ;
2016-02-26 18:28:18 +01:00
}
} ) ;
} ) ) ;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-04-01 15:33:14 +02:00
/*global Backbone, define, window, crypto, CryptoJS */
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
/ * T h i s i s a C o n v e r s e . j s p l u g i n w h i c h a d d s u p p o r t O f f - t h e - r e c o r d ( O T R )
* encryption of one - on - one chat messages .
* /
2016-02-26 18:28:18 +01:00
( function ( root , factory ) {
2016-04-01 15:33:14 +02:00
define ( "converse-otr" , [
"otr" ,
2016-03-19 14:33:31 +01:00
"converse-core" ,
2016-04-01 15:33:14 +02:00
"converse-api"
2016-03-19 14:33:31 +01:00
] , factory ) ;
2016-04-01 15:33:14 +02:00
} ( this , function ( otr , converse , converse _api ) {
2016-02-26 18:28:18 +01:00
"use strict" ;
2016-04-01 15:33:14 +02:00
// Strophe methods for building stanzas
var Strophe = converse _api . env . Strophe ,
2016-03-19 14:33:31 +01:00
utils = converse _api . env . utils ,
2016-04-01 15:33:14 +02:00
b64 _sha1 = converse _api . env . b64 _sha1 ;
// Other necessary globals
var $ = converse _api . env . jQuery ,
_ = converse _api . env . _ ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
// For translations
var _ _ = utils . _ _ . bind ( converse ) ;
var HAS _CSPRNG = ( ( typeof crypto !== 'undefined' ) &&
( ( typeof crypto . randomBytes === 'function' ) ||
( typeof crypto . getRandomValues === 'function' )
) ) ;
var HAS _CRYPTO = HAS _CSPRNG && (
( typeof CryptoJS !== "undefined" ) &&
( typeof otr . OTR !== "undefined" ) &&
( typeof otr . DSA !== "undefined" )
) ;
var UNENCRYPTED = 0 ;
var UNVERIFIED = 1 ;
var VERIFIED = 2 ;
var FINISHED = 3 ;
var OTR _TRANSLATED _MAPPING = { } ; // Populated in initialize
var OTR _CLASS _MAPPING = { } ;
OTR _CLASS _MAPPING [ UNENCRYPTED ] = 'unencrypted' ;
OTR _CLASS _MAPPING [ UNVERIFIED ] = 'unverified' ;
OTR _CLASS _MAPPING [ VERIFIED ] = 'verified' ;
OTR _CLASS _MAPPING [ FINISHED ] = 'finished' ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-otr' , {
2016-02-26 18:28:18 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
2016-04-01 15:33:14 +02:00
_initialize : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . _initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
this . otr = new this . OTR ( ) ;
} ,
2016-02-26 18:28:18 +01:00
registerGlobalEventHandlers : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . registerGlobalEventHandlers ( ) ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
$ ( document ) . click ( function ( ) {
if ( $ ( '.toggle-otr ul' ) . is ( ':visible' ) ) {
$ ( '.toggle-otr ul' , this ) . slideUp ( ) ;
2016-03-29 08:56:13 +02:00
}
2016-04-01 15:33:14 +02:00
if ( $ ( '.toggle-smiley ul' ) . is ( ':visible' ) ) {
$ ( '.toggle-smiley ul' , this ) . slideUp ( ) ;
}
} ) ;
2016-02-26 18:28:18 +01:00
} ,
wrappedChatBox : function ( chatbox ) {
2016-09-16 14:35:02 +02:00
var wrapped _chatbox = this . _ _super _ _ . wrappedChatBox . apply ( this , arguments ) ;
2016-02-26 18:28:18 +01:00
if ( ! chatbox ) { return ; }
2016-04-01 15:33:14 +02:00
return _ . extend ( wrapped _chatbox , {
'endOTR' : chatbox . endOTR . bind ( chatbox ) ,
'initiateOTR' : chatbox . initiateOTR . bind ( chatbox ) ,
} ) ;
2016-02-26 18:28:18 +01:00
} ,
ChatBox : {
initialize : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( this . get ( 'box_id' ) !== 'controlbox' ) {
this . save ( {
'otr_status' : this . get ( 'otr_status' ) || UNENCRYPTED
} ) ;
2016-02-26 18:28:18 +01:00
}
} ,
2016-04-01 15:33:14 +02:00
shouldPlayNotification : function ( $message ) {
/ * D o n ' t p l a y a n o t i f i c a t i o n i f t h i s i s a n O T R m e s s a g e b u t
* encryption is not yet set up . That would mean that the
* OTR session is still being established , so there are no
* "visible" OTR messages being exchanged .
* /
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . shouldPlayNotification . apply ( this , arguments ) &&
2016-06-20 21:11:43 +02:00
! ( utils . isOTRMessage ( $message [ 0 ] ) && ! _ . contains ( [ UNVERIFIED , VERIFIED ] , this . get ( 'otr_status' ) ) ) ;
2016-03-29 08:56:13 +02:00
} ,
2016-06-20 21:11:43 +02:00
createMessage : function ( $message , $delay , original _stanza ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ,
2016-04-01 15:33:14 +02:00
$body = $message . children ( 'body' ) ,
text = ( $body . length > 0 ? $body . text ( ) : undefined ) ;
2016-03-29 08:56:13 +02:00
2016-04-01 15:33:14 +02:00
if ( ( ! text ) || ( ! converse . allow _otr ) ) {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . createMessage . apply ( this , arguments ) ;
2016-03-29 08:56:13 +02:00
}
2016-04-01 15:33:14 +02:00
if ( text . match ( /^\?OTRv23?/ ) ) {
this . initiateOTR ( text ) ;
} else {
if ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . get ( 'otr_status' ) ) ) {
this . otr . receiveMsg ( text ) ;
} else {
if ( text . match ( /^\?OTR/ ) ) {
if ( ! this . otr ) {
this . initiateOTR ( text ) ;
} else {
this . otr . receiveMsg ( text ) ;
}
} else {
// Normal unencrypted message.
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . createMessage . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
}
}
2016-03-29 08:56:13 +02:00
}
} ,
2016-04-01 15:33:14 +02:00
getSession : function ( callback ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
2016-04-01 15:33:14 +02:00
var cipher = CryptoJS . lib . PasswordBasedCipher ;
var pass , instance _tag , saved _key , pass _check ;
if ( converse . cache _otr _key ) {
pass = converse . otr . getSessionPassphrase ( ) ;
if ( typeof pass !== "undefined" ) {
instance _tag = window . sessionStorage [ b64 _sha1 ( this . id + 'instance_tag' ) ] ;
saved _key = window . sessionStorage [ b64 _sha1 ( this . id + 'priv_key' ) ] ;
pass _check = window . sessionStorage [ b64 _sha1 ( this . connection . jid + 'pass_check' ) ] ;
if ( saved _key && instance _tag && typeof pass _check !== 'undefined' ) {
var decrypted = cipher . decrypt ( CryptoJS . algo . AES , saved _key , pass ) ;
var key = otr . DSA . parsePrivate ( decrypted . toString ( CryptoJS . enc . Latin1 ) ) ;
if ( cipher . decrypt ( CryptoJS . algo . AES , pass _check , pass ) . toString ( CryptoJS . enc . Latin1 ) === 'match' ) {
// Verified that the passphrase is still the same
this . trigger ( 'showHelpMessages' , [ _ _ ( 'Re-establishing encrypted session' ) ] ) ;
callback ( {
'key' : key ,
'instance_tag' : instance _tag
} ) ;
return ; // Our work is done here
}
}
}
2016-03-29 08:56:13 +02:00
}
2016-04-01 15:33:14 +02:00
// We need to generate a new key and instance tag
this . trigger ( 'showHelpMessages' , [
_ _ ( 'Generating private key.' ) ,
_ _ ( 'Your browser might become unresponsive.' ) ] ,
null ,
true // show spinner
) ;
window . setTimeout ( function ( ) {
var instance _tag = otr . OTR . makeInstanceTag ( ) ;
callback ( {
'key' : converse . otr . generatePrivateKey . call ( this , instance _tag ) ,
'instance_tag' : instance _tag
} ) ;
} , 500 ) ;
2016-03-29 08:56:13 +02:00
} ,
2016-04-01 15:33:14 +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 ;
2016-03-29 08:56:13 +02:00
}
} ,
2016-04-01 15:33:14 +02:00
onSMP : function ( type , data ) {
// Event handler for SMP (Socialist's Millionaire Protocol)
// used by OTR (off-the-record).
switch ( type ) {
case 'question' :
this . otr . smpSecret ( prompt ( _ _ (
'Authentication request from %1$s\n\nYour chat contact is attempting to verify your identity, by asking you the question below.\n\n%2$s' ,
[ this . get ( 'fullname' ) , data ] ) ) ) ;
break ;
case 'trust' :
if ( data === true ) {
this . save ( { 'otr_status' : VERIFIED } ) ;
} else {
this . trigger (
'showHelpMessages' ,
[ _ _ ( "Could not verify this user's identify." ) ] ,
'error' ) ;
this . save ( { 'otr_status' : UNVERIFIED } ) ;
}
break ;
default :
throw new TypeError ( 'ChatBox.onSMP: Unknown type for SMP' ) ;
}
2016-03-29 08:56:13 +02:00
} ,
2016-04-01 15:33:14 +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 contact. Otherwise, it is us who will
// send the query message to them.
this . save ( { 'otr_status' : UNENCRYPTED } ) ;
this . getSession ( function ( session ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
2016-04-01 15:33:14 +02:00
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' , this . updateOTRStatus . bind ( this ) ) ;
this . otr . on ( 'smp' , this . onSMP . bind ( this ) ) ;
2016-03-29 08:56:13 +02:00
2016-04-01 15:33:14 +02:00
this . otr . on ( 'ui' , function ( msg ) {
this . trigger ( 'showReceivedOTRMessage' , msg ) ;
} . bind ( this ) ) ;
this . otr . on ( 'io' , function ( msg ) {
this . trigger ( 'sendMessage' , new converse . Message ( { message : msg } ) ) ;
} . bind ( this ) ) ;
this . otr . on ( 'error' , function ( msg ) {
this . trigger ( 'showOTRError' , msg ) ;
} . bind ( this ) ) ;
2016-03-29 08:56:13 +02:00
2016-04-01 15:33:14 +02:00
this . trigger ( 'showHelpMessages' , [ _ _ ( 'Exchanging private key with contact.' ) ] ) ;
if ( query _msg ) {
this . otr . receiveMsg ( query _msg ) ;
} else {
this . otr . sendQueryMsg ( ) ;
}
} . bind ( this ) ) ;
2016-03-29 08:56:13 +02:00
} ,
2016-04-01 15:33:14 +02:00
endOTR : function ( ) {
if ( this . otr ) {
this . otr . endOtr ( ) ;
}
this . save ( { 'otr_status' : UNENCRYPTED } ) ;
}
2016-03-29 08:56:13 +02:00
} ,
2016-04-01 15:33:14 +02:00
ChatBoxView : {
events : {
'click .toggle-otr' : 'toggleOTRMenu' ,
'click .start-otr' : 'startOTRFromToolbar' ,
'click .end-otr' : 'endOTR' ,
'click .auth-otr' : 'authOTR'
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
this . model . on ( 'change:otr_status' , this . onOTRStatusChanged , this ) ;
this . model . on ( 'showOTRError' , this . showOTRError , this ) ;
this . model . on ( 'showSentOTRMessage' , function ( text ) {
this . showMessage ( { 'message' : text , 'sender' : 'me' } ) ;
} , this ) ;
this . model . on ( 'showReceivedOTRMessage' , function ( text ) {
this . showMessage ( { 'message' : text , 'sender' : 'them' } ) ;
} , this ) ;
if ( ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . model . get ( 'otr_status' ) ) ) || converse . use _otr _by _default ) {
this . model . initiateOTR ( ) ;
2016-02-26 18:28:18 +01:00
}
2016-03-19 14:33:31 +01:00
} ,
2016-04-01 15:33:14 +02:00
createMessageStanza : function ( ) {
2016-09-16 14:35:02 +02:00
var stanza = this . _ _super _ _ . createMessageStanza . apply ( this , arguments ) ;
2016-06-20 21:11:43 +02:00
if ( this . model . get ( 'otr_status' ) !== UNENCRYPTED || utils . isOTRMessage ( stanza . nodeTree ) ) {
2016-04-01 15:33:14 +02:00
// OTR messages aren't carbon copied
2016-06-20 21:11:43 +02:00
stanza . c ( 'private' , { 'xmlns' : Strophe . NS . CARBONS } ) . up ( )
. c ( 'no-store' , { 'xmlns' : Strophe . NS . HINTS } ) . up ( )
. c ( 'no-permanent-store' , { 'xmlns' : Strophe . NS . HINTS } ) . up ( )
. c ( 'no-copy' , { 'xmlns' : Strophe . NS . HINTS } ) ;
2016-04-01 15:33:14 +02:00
}
return stanza ;
2016-03-30 14:03:15 +02:00
} ,
2016-04-01 15:33:14 +02:00
onMessageSubmitted : function ( text ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
2016-04-01 15:33:14 +02:00
if ( ! converse . connection . authenticated ) {
return this . showHelpMessages (
[ 'Sorry, the connection has been lost, ' +
'and your message could not be sent' ] ,
'error'
) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
var match = text . replace ( /^\s*/ , "" ) . match ( /^\/(.*)\s*$/ ) ;
if ( match ) {
if ( ( converse . allow _otr ) && ( match [ 1 ] === "endotr" ) ) {
return this . endOTR ( ) ;
} else if ( ( converse . allow _otr ) && ( match [ 1 ] === "otr" ) ) {
return this . model . initiateOTR ( ) ;
2016-02-26 18:28:18 +01:00
}
}
2016-04-01 15:33:14 +02:00
if ( _ . contains ( [ UNVERIFIED , VERIFIED ] , this . model . get ( 'otr_status' ) ) ) {
// Off-the-record encryption is active
this . model . otr . sendMsg ( text ) ;
this . model . trigger ( 'showSentOTRMessage' , text ) ;
} else {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . onMessageSubmitted . apply ( this , arguments ) ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
} ,
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
onOTRStatusChanged : function ( ) {
this . renderToolbar ( ) . informOTRChange ( ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +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 ) {
msgs . push ( _ _ ( "Your messages are now encrypted but your contact's identity has not been verified." ) ) ;
} else if ( data . otr _status === VERIFIED ) {
msgs . push ( _ _ ( "Your contact's identify has been verified." ) ) ;
} else if ( data . otr _status === FINISHED ) {
msgs . push ( _ _ ( "Your contact has ended encryption on their end, you should do the same." ) ) ;
}
return this . showHelpMessages ( msgs , 'info' , false ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
showOTRError : function ( msg ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
2016-04-01 15:33:14 +02:00
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' ) ;
} else if ( msg === 'Received an unreadable encrypted message.' ) {
this . showHelpMessages (
[ _ _ ( 'We received an unreadable encrypted message' ) ] ,
'error' ) ;
2016-03-19 14:33:31 +01:00
} else {
2016-04-01 15:33:14 +02:00
this . showHelpMessages ( [ 'Encryption error occured: ' + msg ] , 'error' ) ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
converse . log ( "OTR ERROR:" + msg ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
startOTRFromToolbar : function ( ev ) {
$ ( ev . target ) . parent ( ) . parent ( ) . slideUp ( ) ;
ev . stopPropagation ( ) ;
this . model . initiateOTR ( ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
endOTR : function ( ev ) {
if ( typeof ev !== "undefined" ) {
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
}
this . model . endOTR ( ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
authOTR : function ( ev ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
2016-04-01 15:33:14 +02:00
var scheme = $ ( ev . target ) . data ( ) . scheme ;
var result , question , answer ;
if ( scheme === 'fingerprint' ) {
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.' , [
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' ) {
alert ( _ _ ( 'You will be prompted to provide a security question and then an answer to that question.\n\nYour contact will then be prompted the same question and if they type the exact same answer (case sensitive), their identity will be verified.' ) ) ;
question = prompt ( _ _ ( 'What is your security question?' ) ) ;
if ( question ) {
answer = prompt ( _ _ ( 'What is the answer to the security question?' ) ) ;
this . model . otr . smpSecret ( answer , question ) ;
}
2016-03-29 08:56:13 +02:00
} else {
2016-04-01 15:33:14 +02:00
this . showHelpMessages ( [ _ _ ( 'Invalid authentication scheme provided' ) ] , 'error' ) ;
2016-03-29 08:56:13 +02:00
}
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
toggleOTRMenu : function ( ev ) {
ev . stopPropagation ( ) ;
this . $el . find ( '.toggle-otr ul' ) . slideToggle ( 200 ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
getOTRTooltip : function ( ) {
var data = this . model . toJSON ( ) ;
if ( data . otr _status === UNENCRYPTED ) {
return _ _ ( 'Your messages are not encrypted. Click here to enable OTR encryption.' ) ;
} else if ( data . otr _status === UNVERIFIED ) {
return _ _ ( 'Your messages are encrypted, but your contact has not been verified.' ) ;
} else if ( data . otr _status === VERIFIED ) {
return _ _ ( 'Your messages are encrypted and your contact verified.' ) ;
} else if ( data . otr _status === FINISHED ) {
return _ _ ( 'Your contact has closed their end of the private session, you should do the same' ) ;
2016-02-26 18:28:18 +01:00
}
} ,
2016-04-01 15:33:14 +02:00
renderToolbar : function ( options ) {
2016-09-16 14:35:02 +02:00
var converse = this . _ _super _ _ . converse ;
2016-04-01 15:33:14 +02:00
if ( ! converse . show _toolbar ) {
2016-03-19 14:33:31 +01:00
return ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
var data = this . model . toJSON ( ) ;
options = _ . extend ( options || { } , {
FINISHED : FINISHED ,
UNENCRYPTED : UNENCRYPTED ,
UNVERIFIED : UNVERIFIED ,
VERIFIED : VERIFIED ,
// FIXME: Leaky abstraction MUC
allow _otr : converse . allow _otr && ! this . is _chatroom ,
label _end _encrypted _conversation : _ _ ( 'End encrypted conversation' ) ,
label _refresh _encrypted _conversation : _ _ ( 'Refresh encrypted conversation' ) ,
label _start _encrypted _conversation : _ _ ( 'Start encrypted conversation' ) ,
label _verify _with _fingerprints : _ _ ( 'Verify with fingerprints' ) ,
label _verify _with _smp : _ _ ( 'Verify with SMP' ) ,
label _whats _this : _ _ ( "What\'s this?" ) ,
otr _status _class : OTR _CLASS _MAPPING [ data . otr _status ] ,
otr _tooltip : this . getOTRTooltip ( ) ,
otr _translated _status : OTR _TRANSLATED _MAPPING [ data . otr _status ] ,
2016-03-19 14:33:31 +01:00
} ) ;
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . renderToolbar . call ( this , options ) ;
2016-04-01 15:33:14 +02:00
this . $el . find ( '.chat-toolbar' ) . append (
converse . templates . toolbar _otr (
_ . extend ( this . model . toJSON ( ) , options || { } )
) ) ;
return this ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
}
} ,
2016-02-28 10:00:22 +01:00
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
var converse = this . converse ;
// Translation aware constants
// ---------------------------
// We can only call the __ translation method *after* converse.js
// has been initialized and with it the i18n machinery. That's why
// we do it here in the "initialize" method and not at the top of
// the module.
OTR _TRANSLATED _MAPPING [ UNENCRYPTED ] = _ _ ( 'unencrypted' ) ;
OTR _TRANSLATED _MAPPING [ UNVERIFIED ] = _ _ ( 'unverified' ) ;
OTR _TRANSLATED _MAPPING [ VERIFIED ] = _ _ ( 'verified' ) ;
OTR _TRANSLATED _MAPPING [ FINISHED ] = _ _ ( 'finished' ) ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
// For translations
_ _ = utils . _ _ . bind ( converse ) ;
// Configuration values for this plugin
var settings = {
allow _otr : true ,
cache _otr _key : false ,
use _otr _by _default : false
} ;
_ . extend ( converse . default _settings , settings ) ;
_ . extend ( converse , settings ) ;
_ . extend ( converse , _ . pick ( converse . user _settings , Object . keys ( settings ) ) ) ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
// Only allow OTR if we have the capability
converse . allow _otr = converse . allow _otr && HAS _CRYPTO ;
// Only use OTR by default if allow OTR is enabled to begin with
converse . use _otr _by _default = converse . use _otr _by _default && converse . allow _otr ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
// Backbone Models and Views
// -------------------------
converse . OTR = Backbone . Model . extend ( {
// A model for managing OTR settings.
getSessionPassphrase : function ( ) {
if ( converse . authentication === 'prebind' ) {
var key = b64 _sha1 ( converse . connection . jid ) ,
pass = window . sessionStorage [ key ] ;
if ( typeof pass === 'undefined' ) {
pass = Math . floor ( Math . random ( ) * 4294967295 ) . toString ( ) ;
window . sessionStorage [ key ] = pass ;
}
return pass ;
2016-03-19 14:33:31 +01:00
} else {
2016-04-01 15:33:14 +02:00
return converse . connection . pass ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
} ,
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
generatePrivateKey : function ( instance _tag ) {
var key = new otr . DSA ( ) ;
var jid = converse . connection . jid ;
if ( converse . cache _otr _key ) {
var cipher = CryptoJS . lib . PasswordBasedCipher ;
var pass = this . getSessionPassphrase ( ) ;
if ( typeof pass !== "undefined" ) {
// Encrypt the key and set in sessionStorage. Also store instance tag.
window . sessionStorage [ b64 _sha1 ( jid + 'priv_key' ) ] =
cipher . encrypt ( CryptoJS . algo . AES , key . packPrivate ( ) , pass ) . toString ( ) ;
window . sessionStorage [ b64 _sha1 ( jid + 'instance_tag' ) ] = instance _tag ;
window . sessionStorage [ b64 _sha1 ( jid + 'pass_check' ) ] =
cipher . encrypt ( CryptoJS . algo . AES , 'match' , pass ) . toString ( ) ;
}
}
return key ;
2016-03-29 08:56:13 +02:00
}
2016-03-19 14:33:31 +01:00
} ) ;
2016-02-26 18:28:18 +01:00
}
} ) ;
} ) ) ;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-04-01 15:33:14 +02:00
/*global Backbone, define */
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
/ * T h i s i s a C o n v e r s e . j s p l u g i n w h i c h a d d s u p p o r t f o r i n - b a n d r e g i s t r a t i o n
* as specified in XEP - 0077.
2016-02-26 18:28:18 +01:00
* /
( function ( root , factory ) {
2016-04-01 15:33:14 +02:00
define ( "converse-register" , [
2016-03-07 18:54:07 +01:00
"converse-core" ,
"converse-api" ,
2016-04-01 15:33:14 +02:00
"converse-controlbox"
2016-03-07 18:54:07 +01:00
] , factory ) ;
2016-04-01 15:33:14 +02:00
} ( this , function ( converse , converse _api ) {
2016-02-26 18:28:18 +01:00
"use strict" ;
// Strophe methods for building stanzas
var Strophe = converse _api . env . Strophe ,
utils = converse _api . env . utils ,
2016-04-01 15:33:14 +02:00
$iq = converse _api . env . $iq ;
2016-02-26 18:28:18 +01:00
// Other necessary globals
var $ = converse _api . env . jQuery ,
_ = converse _api . env . _ ;
// For translations
var _ _ = utils . _ _ . bind ( converse ) ;
2016-04-01 15:33:14 +02:00
// Add Strophe Namespaces
Strophe . addNamespace ( 'REGISTER' , 'jabber:iq:register' ) ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
// Add Strophe Statuses
var i = 0 ;
Object . keys ( Strophe . Status ) . forEach ( function ( key ) {
i = Math . max ( i , Strophe . Status [ key ] ) ;
} ) ;
Strophe . Status . REGIFAIL = i + 1 ;
Strophe . Status . REGISTERED = i + 2 ;
Strophe . Status . CONFLICT = i + 3 ;
Strophe . Status . NOTACCEPTABLE = i + 5 ;
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-register' , {
2016-02-26 18:28:18 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
2016-04-01 15:33:14 +02:00
ControlBoxView : {
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
renderLoginPanel : function ( ) {
/ * A l s o r e n d e r a r e g i s t r a t i o n p a n e l , w h e n r e n d e r i n g t h e
* login panel .
* /
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . renderLoginPanel . apply ( this , arguments ) ;
var converse = this . _ _super _ _ . converse ;
2016-04-01 15:33:14 +02:00
if ( converse . allow _registration ) {
2016-05-03 17:37:10 +02:00
this . registerpanel = new converse . RegisterPanel ( {
2016-04-01 15:33:14 +02:00
'$parent' : this . $el . find ( '.controlbox-panes' ) ,
'model' : this
2016-05-03 17:37:10 +02:00
} ) ;
2016-04-01 15:33:14 +02:00
this . registerpanel . render ( ) . $el . hide ( ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return this ;
}
}
} ,
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
var converse = this . converse ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
this . updateSettings ( {
allow _registration : true ,
domain _placeholder : _ _ ( " e.g. conversejs.org" ) , // Placeholder text shown in the domain input on the registration form
providers _link : 'https://xmpp.net/directory.php' , // Link to XMPP providers shown on registration page
} ) ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
converse . RegisterPanel = Backbone . View . extend ( {
tagName : 'div' ,
id : "register" ,
className : 'controlbox-pane' ,
events : {
'submit form#converse-register' : 'onProviderChosen'
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
initialize : function ( cfg ) {
this . reset ( ) ;
this . $parent = cfg . $parent ;
this . $tabs = cfg . $parent . parent ( ) . find ( '#controlbox-tabs' ) ;
this . registerHooks ( ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
render : function ( ) {
this . $parent . append ( this . $el . html (
converse . templates . register _panel ( {
'label_domain' : _ _ ( "Your XMPP provider's domain name:" ) ,
'label_register' : _ _ ( 'Fetch registration form' ) ,
'help_providers' : _ _ ( 'Tip: A list of public XMPP providers is available' ) ,
'help_providers_link' : _ _ ( 'here' ) ,
'href_providers' : converse . providers _link ,
'domain_placeholder' : converse . domain _placeholder
} )
) ) ;
this . $tabs . append ( converse . templates . register _tab ( { label _register : _ _ ( 'Register' ) } ) ) ;
return this ;
} ,
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
registerHooks : function ( ) {
/ * H o o k i n t o S t r o p h e ' s _ c o n n e c t _ c b , s o t h a t w e c a n s e n d a n I Q
* requesting the registration fields .
* /
var conn = converse . connection ;
var connect _cb = conn . _connect _cb . bind ( conn ) ;
conn . _connect _cb = function ( req , callback , raw ) {
if ( ! this . _registering ) {
connect _cb ( req , callback , raw ) ;
2016-02-26 18:28:18 +01:00
} else {
2016-04-01 15:33:14 +02:00
if ( this . getRegistrationFields ( req , callback , raw ) ) {
this . _registering = false ;
2016-02-26 18:28:18 +01:00
}
}
2016-04-01 15:33:14 +02:00
} . bind ( this ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-04-01 15:33:14 +02:00
getRegistrationFields : function ( req , _callback , raw ) {
/ * S e n d a n I Q s t a n z a t o t h e X M P P s e r v e r a s k i n g f o r t h e
* registration fields .
* Parameters :
* ( Strophe . Request ) req - The current request
* ( Function ) callback
* /
converse . log ( "sendQueryStanza was called" ) ;
var conn = converse . connection ;
conn . connected = true ;
var body = conn . _proto . _reqToData ( req ) ;
if ( ! body ) { return ; }
if ( conn . _proto . _connect _cb ( body ) === Strophe . Status . CONNFAIL ) {
return false ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
var register = body . getElementsByTagName ( "register" ) ;
var mechanisms = body . getElementsByTagName ( "mechanism" ) ;
if ( register . length === 0 && mechanisms . length === 0 ) {
conn . _proto . _no _auth _received ( _callback ) ;
return false ;
}
if ( register . length === 0 ) {
conn . _changeConnectStatus (
Strophe . Status . REGIFAIL ,
_ _ ( 'Sorry, the given provider does not support in band account registration. Please try with a different provider.' )
) ;
return true ;
}
// Send an IQ stanza to get all required data fields
conn . _addSysHandler ( this . onRegistrationFields . bind ( this ) , null , "iq" , null , null ) ;
conn . send ( $iq ( { type : "get" } ) . c ( "query" , { xmlns : Strophe . NS . REGISTER } ) . tree ( ) ) ;
return true ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
onRegistrationFields : function ( stanza ) {
/ * H a n d l e r f o r R e g i s t r a t i o n F i e l d s R e q u e s t .
*
* Parameters :
* ( XMLElement ) elem - The query stanza .
* /
if ( stanza . getElementsByTagName ( "query" ) . length !== 1 ) {
converse . connection . _changeConnectStatus ( Strophe . Status . REGIFAIL , "unknown" ) ;
return false ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
this . setFields ( stanza ) ;
this . renderRegistrationForm ( stanza ) ;
return false ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
reset : function ( settings ) {
var defaults = {
fields : { } ,
urls : [ ] ,
title : "" ,
instructions : "" ,
registered : false ,
_registering : false ,
domain : null ,
form _type : null
} ;
_ . extend ( this , defaults ) ;
if ( settings ) {
_ . extend ( this , _ . pick ( settings , Object . keys ( defaults ) ) ) ;
2016-02-26 18:28:18 +01:00
}
} ,
2016-04-01 15:33:14 +02:00
onProviderChosen : function ( ev ) {
/ * C a l l b a c k m e t h o d t h a t g e t s c a l l e d w h e n t h e u s e r h a s c h o s e n a n
* XMPP provider .
*
* Parameters :
* ( Submit Event ) ev - Form submission event .
* /
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var $form = $ ( ev . target ) ,
$domain _input = $form . find ( 'input[name=domain]' ) ,
domain = $domain _input . val ( ) ;
if ( ! domain ) {
$domain _input . addClass ( 'error' ) ;
return ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
$form . find ( 'input[type=submit]' ) . hide ( )
. after ( converse . templates . registration _request ( {
cancel : _ _ ( 'Cancel' ) ,
info _message : _ _ ( 'Requesting a registration form from the XMPP server' )
} ) ) ;
$form . find ( 'button.cancel' ) . on ( 'click' , this . cancelRegistration . bind ( this ) ) ;
this . reset ( {
domain : Strophe . getDomainFromJid ( domain ) ,
_registering : true
} ) ;
converse . connection . connect ( this . domain , "" , this . onRegistering . bind ( this ) ) ;
return false ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
giveFeedback : function ( message , klass ) {
this . $ ( '.reg-feedback' ) . attr ( 'class' , 'reg-feedback' ) . text ( message ) ;
if ( klass ) {
$ ( '.reg-feedback' ) . addClass ( klass ) ;
2016-02-26 18:28:18 +01:00
}
} ,
2016-04-01 15:33:14 +02:00
onRegistering : function ( status , error ) {
var that ;
converse . log ( 'onRegistering' ) ;
if ( _ . contains ( [
Strophe . Status . DISCONNECTED ,
Strophe . Status . CONNFAIL ,
Strophe . Status . REGIFAIL ,
Strophe . Status . NOTACCEPTABLE ,
Strophe . Status . CONFLICT
] , status ) ) {
converse . log ( 'Problem during registration: Strophe.Status is: ' + status ) ;
this . cancelRegistration ( ) ;
if ( error ) {
this . giveFeedback ( error , 'error' ) ;
} else {
this . giveFeedback ( _ _ (
'Something went wrong while establishing a connection with "%1$s". Are you sure it exists?' ,
this . domain
) , 'error' ) ;
}
} else if ( status === Strophe . Status . REGISTERED ) {
converse . log ( "Registered successfully." ) ;
converse . connection . reset ( ) ;
that = this ;
this . $ ( 'form' ) . hide ( function ( ) {
$ ( this ) . replaceWith ( '<span class="spinner centered"/>' ) ;
if ( that . fields . password && that . fields . username ) {
// automatically log the user in
converse . connection . connect (
that . fields . username . toLowerCase ( ) + '@' + that . domain . toLowerCase ( ) ,
that . fields . password ,
converse . onConnectStatusChanged
) ;
converse . chatboxviews . get ( 'controlbox' )
. switchTab ( { target : that . $tabs . find ( '.current' ) } )
. giveFeedback ( _ _ ( 'Now logging you in' ) ) ;
} else {
converse . chatboxviews . get ( 'controlbox' )
. renderLoginPanel ( )
. giveFeedback ( _ _ ( 'Registered successfully' ) ) ;
}
that . reset ( ) ;
} ) ;
2016-03-19 14:33:31 +01:00
}
} ,
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
renderRegistrationForm : function ( stanza ) {
/ * R e n d e r s t h e r e g i s t r a t i o n f o r m b a s e d o n t h e X F o r m f i e l d s
* received from the XMPP server .
*
* Parameters :
* ( XMLElement ) stanza - The IQ stanza received from the XMPP server .
* /
var $form = this . $ ( 'form' ) ,
$stanza = $ ( stanza ) ,
$fields , $input ;
$form . empty ( ) . append ( converse . templates . registration _form ( {
'domain' : this . domain ,
'title' : this . title ,
'instructions' : this . instructions
} ) ) ;
if ( this . form _type === 'xform' ) {
$fields = $stanza . find ( 'field' ) ;
_ . each ( $fields , function ( field ) {
$form . append ( utils . xForm2webForm . bind ( this , $ ( field ) , $stanza ) ) ;
} . bind ( this ) ) ;
} else {
// Show fields
_ . each ( Object . keys ( this . fields ) , function ( key ) {
if ( key === "username" ) {
$input = converse . templates . form _username ( {
domain : ' @' + this . domain ,
name : key ,
type : "text" ,
label : key ,
value : '' ,
required : 1
} ) ;
} else {
$form . append ( '<label>' + key + '</label>' ) ;
$input = $ ( '<input placeholder="' + key + '" name="' + key + '"></input>' ) ;
if ( key === 'password' || key === 'email' ) {
$input . attr ( 'type' , key ) ;
}
}
$form . append ( $input ) ;
} . bind ( this ) ) ;
// Show urls
_ . each ( this . urls , function ( url ) {
$form . append ( $ ( '<a target="blank"></a>' ) . attr ( 'href' , url ) . text ( url ) ) ;
} . bind ( this ) ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
if ( this . fields ) {
$form . append ( '<input type="submit" class="pure-button button-primary" value="' + _ _ ( 'Register' ) + '"/>' ) ;
$form . on ( 'submit' , this . submitRegistrationForm . bind ( this ) ) ;
$form . append ( '<input type="button" class="pure-button button-cancel" value="' + _ _ ( 'Cancel' ) + '"/>' ) ;
$form . find ( 'input[type=button]' ) . on ( 'click' , this . cancelRegistration . bind ( this ) ) ;
2016-03-19 14:33:31 +01:00
} else {
2016-04-01 15:33:14 +02:00
$form . append ( '<input type="button" class="submit" value="' + _ _ ( 'Return' ) + '"/>' ) ;
$form . find ( 'input[type=button]' ) . on ( 'click' , this . cancelRegistration . bind ( this ) ) ;
2016-02-26 18:28:18 +01:00
}
} ,
2016-04-01 15:33:14 +02:00
reportErrors : function ( stanza ) {
/ * R e p o r t b a c k t o t h e u s e r a n y e r r o r m e s s a g e s r e c e i v e d f r o m t h e
* XMPP server after attempted registration .
*
* Parameters :
* ( XMLElement ) stanza - The IQ stanza received from the
* XMPP server .
* /
var $form = this . $ ( 'form' ) , flash ;
var $errmsgs = $ ( stanza ) . find ( 'error text' ) ;
var $flash = $form . find ( '.form-errors' ) ;
if ( ! $flash . length ) {
flash = '<legend class="form-errors"></legend>' ;
if ( $form . find ( 'p.instructions' ) . length ) {
$form . find ( 'p.instructions' ) . append ( flash ) ;
} else {
$form . prepend ( flash ) ;
}
$flash = $form . find ( '.form-errors' ) ;
} else {
$flash . empty ( ) ;
}
$errmsgs . each ( function ( idx , txt ) {
$flash . append ( $ ( '<p>' ) . text ( $ ( txt ) . text ( ) ) ) ;
} ) ;
if ( ! $errmsgs . length ) {
$flash . append ( $ ( '<p>' ) . text (
_ _ ( 'The provider rejected your registration attempt. ' +
'Please check the values you entered for correctness.' ) ) ) ;
}
$flash . show ( ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
cancelRegistration : function ( ev ) {
/ * H a n d l e r , w h e n t h e u s e r c a n c e l s t h e r e g i s t r a t i o n f o r m .
* /
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
converse . connection . reset ( ) ;
this . render ( ) ;
} ,
submitRegistrationForm : function ( ev ) {
/ * H a n d l e r , w h e n t h e u s e r s u b m i t s t h e r e g i s t r a t i o n f o r m .
* Provides form error feedback or starts the registration
* process .
*
* Parameters :
* ( Event ) ev - the submit event .
* /
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
var $empty _inputs = this . $ ( 'input.required:emptyVal' ) ;
if ( $empty _inputs . length ) {
$empty _inputs . addClass ( 'error' ) ;
return ;
}
var $inputs = $ ( ev . target ) . find ( ':input:not([type=button]):not([type=submit])' ) ,
iq = $iq ( { type : "set" } ) . c ( "query" , { xmlns : Strophe . NS . REGISTER } ) ;
if ( this . form _type === 'xform' ) {
iq . c ( "x" , { xmlns : Strophe . NS . XFORM , type : 'submit' } ) ;
$inputs . each ( function ( ) {
iq . cnode ( utils . webForm2xForm ( this ) ) . up ( ) ;
} ) ;
2016-02-26 18:28:18 +01:00
} else {
2016-04-01 15:33:14 +02:00
$inputs . each ( function ( ) {
var $input = $ ( this ) ;
iq . c ( $input . attr ( 'name' ) , { } , $input . val ( ) ) ;
} ) ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
converse . connection . _addSysHandler ( this . _onRegisterIQ . bind ( this ) , null , "iq" , null , null ) ;
converse . connection . send ( iq ) ;
this . setFields ( iq . tree ( ) ) ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
setFields : function ( stanza ) {
/ * S t o r e s t h e v a l u e s t h a t w i l l b e s e n t t o t h e X M P P s e r v e r
* during attempted registration .
*
* Parameters :
* ( XMLElement ) stanza - the IQ stanza that will be sent to the XMPP server .
* /
var $query = $ ( stanza ) . find ( 'query' ) , $xform ;
if ( $query . length > 0 ) {
$xform = $query . find ( 'x[xmlns="' + Strophe . NS . XFORM + '"]' ) ;
if ( $xform . length > 0 ) {
this . _setFieldsFromXForm ( $xform ) ;
} else {
this . _setFieldsFromLegacy ( $query ) ;
}
}
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
_setFieldsFromLegacy : function ( $query ) {
$query . children ( ) . each ( function ( idx , field ) {
var $field = $ ( field ) ;
if ( field . tagName . toLowerCase ( ) === 'instructions' ) {
this . instructions = Strophe . getText ( field ) ;
return ;
} else if ( field . tagName . toLowerCase ( ) === 'x' ) {
if ( $field . attr ( 'xmlns' ) === 'jabber:x:oob' ) {
$field . find ( 'url' ) . each ( function ( idx , url ) {
this . urls . push ( $ ( url ) . text ( ) ) ;
} . bind ( this ) ) ;
}
return ;
}
this . fields [ field . tagName . toLowerCase ( ) ] = Strophe . getText ( field ) ;
} . bind ( this ) ) ;
this . form _type = 'legacy' ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
_setFieldsFromXForm : function ( $xform ) {
this . title = $xform . find ( 'title' ) . text ( ) ;
this . instructions = $xform . find ( 'instructions' ) . text ( ) ;
$xform . find ( 'field' ) . each ( function ( idx , field ) {
var _var = field . getAttribute ( 'var' ) ;
if ( _var ) {
this . fields [ _var . toLowerCase ( ) ] = $ ( field ) . children ( 'value' ) . text ( ) ;
2016-02-26 18:28:18 +01:00
} else {
2016-04-01 15:33:14 +02:00
// TODO: other option seems to be type="fixed"
converse . log ( "WARNING: Found field we couldn't parse" ) ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
} . bind ( this ) ) ;
this . form _type = 'xform' ;
} ,
_onRegisterIQ : function ( stanza ) {
/ * C a l l b a c k m e t h o d t h a t g e t s c a l l e d w h e n a r e t u r n I Q s t a n z a
* is received from the XMPP server , after attempting to
* register a new user .
*
* Parameters :
* ( XMLElement ) stanza - The IQ stanza .
* /
var error = null ,
query = stanza . getElementsByTagName ( "query" ) ;
if ( query . length > 0 ) {
query = query [ 0 ] ;
}
if ( stanza . getAttribute ( "type" ) === "error" ) {
converse . log ( "Registration failed." ) ;
error = stanza . getElementsByTagName ( "error" ) ;
if ( error . length !== 1 ) {
converse . connection . _changeConnectStatus ( Strophe . Status . REGIFAIL , "unknown" ) ;
return false ;
}
error = error [ 0 ] . firstChild . tagName . toLowerCase ( ) ;
if ( error === 'conflict' ) {
converse . connection . _changeConnectStatus ( Strophe . Status . CONFLICT , error ) ;
} else if ( error === 'not-acceptable' ) {
converse . connection . _changeConnectStatus ( Strophe . Status . NOTACCEPTABLE , error ) ;
} else {
converse . connection . _changeConnectStatus ( Strophe . Status . REGIFAIL , error ) ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
this . reportErrors ( stanza ) ;
2016-02-26 18:28:18 +01:00
} else {
2016-04-01 15:33:14 +02:00
converse . connection . _changeConnectStatus ( Strophe . Status . REGISTERED , null ) ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
return false ;
2016-02-26 18:28:18 +01:00
} ,
2016-04-01 15:33:14 +02:00
remove : function ( ) {
this . $tabs . empty ( ) ;
this . $el . parent ( ) . empty ( ) ;
}
} ) ;
}
} ) ;
} ) ) ;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
/ * T h i s i s a C o n v e r s e . j s p l u g i n w h i c h a d d s u p p o r t f o r a p p l i c a t i o n - l e v e l p i n g s
* as specified in XEP - 0199 XMPP Ping .
* /
( function ( root , factory ) {
define ( "converse-ping" , [
"converse-core" ,
"converse-api" ,
"strophe.ping"
] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
// Strophe methods for building stanzas
var Strophe = converse _api . env . Strophe ;
// Other necessary globals
var _ = converse _api . env . _ ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-ping' , {
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
var converse = this . converse ;
this . updateSettings ( {
ping _interval : 180 //in seconds
} ) ;
converse . ping = function ( jid , success , error , timeout ) {
// XXX: We could first check here if the server advertised that
// it supports PING.
// However, some servers don't advertise while still keeping the
// connection option due to pings.
//
// var feature = converse.features.findWhere({'var': Strophe.NS.PING});
converse . lastStanzaDate = new Date ( ) ;
if ( typeof jid === 'undefined' || jid === null ) {
jid = Strophe . getDomainFromJid ( converse . bare _jid ) ;
}
if ( typeof timeout === 'undefined' ) { timeout = null ; }
if ( typeof success === 'undefined' ) { success = null ; }
if ( typeof error === 'undefined' ) { error = null ; }
if ( converse . connection ) {
converse . connection . ping . ping ( jid , success , error , timeout ) ;
return true ;
}
return false ;
} ;
converse . pong = function ( ping ) {
converse . lastStanzaDate = new Date ( ) ;
converse . connection . ping . pong ( ping ) ;
return true ;
} ;
converse . registerPongHandler = function ( ) {
converse . connection . disco . addFeature ( Strophe . NS . PING ) ;
converse . connection . ping . addPingHandler ( converse . pong ) ;
} ;
converse . registerPingHandler = function ( ) {
converse . registerPongHandler ( ) ;
if ( converse . ping _interval > 0 ) {
converse . connection . addHandler ( function ( ) {
/ * H a n d l e r o n e a c h s t a n z a , s a v e s t h e r e c e i v e d d a t e
* in order to ping only when needed .
* /
converse . lastStanzaDate = new Date ( ) ;
return true ;
} ) ;
converse . connection . addTimedHandler ( 1000 , function ( ) {
var now = new Date ( ) ;
if ( ! converse . lastStanzaDate ) {
converse . lastStanzaDate = now ;
}
if ( ( now - converse . lastStanzaDate ) / 1000 > converse . ping _interval ) {
return converse . ping ( ) ;
}
return true ;
} ) ;
}
} ;
_ . extend ( converse _api , {
/ * W e e x t e n d t h e d e f a u l t c o n v e r s e . j s A P I t o a d d a m e t h o d s p e c i f i c
* to this plugin .
* /
'ping' : function ( jid ) {
converse . ping ( jid ) ;
}
} ) ;
var onConnected = function ( ) {
// Wrapper so that we can spy on registerPingHandler in tests
converse . registerPingHandler ( ) ;
} ;
converse . on ( 'connected' , onConnected ) ;
converse . on ( 'reconnected' , onConnected ) ;
}
} ) ;
} ) ) ;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
( function ( root , factory ) {
define ( "converse-notification" , [ "converse-core" , "converse-api" ] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
var $ = converse _api . env . jQuery ,
utils = converse _api . env . utils ,
Strophe = converse _api . env . Strophe ,
_ = converse _api . env . _ ;
// For translations
var _ _ = utils . _ _ . bind ( converse ) ;
var _ _ _ = utils . _ _ _ ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-notification' , {
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
var converse = this . converse ;
2016-06-20 21:11:43 +02:00
converse . supports _html5 _notification = "Notification" in window ;
2016-04-01 15:33:14 +02:00
this . updateSettings ( {
2016-06-20 21:11:43 +02:00
notify _all _room _messages : false ,
2016-04-01 15:33:14 +02:00
show _desktop _notifications : true ,
chatstate _notification _blacklist : [ ] ,
// ^ a list of JIDs to ignore concerning chat state notifications
play _sounds : false ,
sounds _path : '/sounds/' ,
2016-07-26 08:00:30 +02:00
notification _icon : '/logo/conversejs128.png'
2016-04-01 15:33:14 +02:00
} ) ;
converse . isOnlyChatStateNotification = function ( $msg ) {
// See XEP-0085 Chat State Notification
return (
$msg . find ( 'body' ) . length === 0 && (
$msg . find ( converse . ACTIVE ) . length !== 0 ||
$msg . find ( converse . COMPOSING ) . length !== 0 ||
$msg . find ( converse . INACTIVE ) . length !== 0 ||
$msg . find ( converse . PAUSED ) . length !== 0 ||
$msg . find ( converse . GONE ) . length !== 0
)
) ;
} ;
converse . shouldNotifyOfGroupMessage = function ( $message ) {
/ * I s t h i s a g r o u p m e s s a g e w o r t h y o f n o t i f i c a t i o n ?
* /
2016-06-20 21:11:43 +02:00
var notify _all = converse . notify _all _room _messages ,
jid = $message . attr ( 'from' ) ,
2016-04-01 15:33:14 +02:00
resource = Strophe . getResourceFromJid ( jid ) ,
2016-06-20 21:11:43 +02:00
room _jid = Strophe . getBareJidFromJid ( jid ) ,
2016-04-01 15:33:14 +02:00
sender = resource && Strophe . unescapeNode ( resource ) || '' ;
if ( sender === '' || $message . find ( 'delay' ) . length > 0 ) {
return false ;
}
2016-06-20 21:11:43 +02:00
var room = converse . chatboxes . get ( room _jid ) ;
var $body = $message . children ( 'body' ) ;
if ( ! $body . length ) {
return false ;
}
var mentioned = ( new RegExp ( "\\b" + room . get ( 'nick' ) + "\\b" ) ) . test ( $body . text ( ) ) ;
notify _all = notify _all === true || ( _ . isArray ( notify _all ) && _ . contains ( notify _all , room _jid ) ) ;
if ( sender === room . get ( 'nick' ) || ( ! notify _all && ! mentioned ) ) {
2016-04-01 15:33:14 +02:00
return false ;
}
return true ;
} ;
converse . shouldNotifyOfMessage = function ( message ) {
/ * I s t h i s a m e s s a g e w o r t h y o f n o t i f i c a t i o n ?
* /
2016-06-20 21:11:43 +02:00
if ( utils . isOTRMessage ( message ) ) {
return false ;
}
2016-04-01 15:33:14 +02:00
var $message = $ ( message ) ,
$forwarded = $message . find ( 'forwarded' ) ;
if ( $forwarded . length ) {
return false ;
} else if ( $message . attr ( 'type' ) === 'groupchat' ) {
return converse . shouldNotifyOfGroupMessage ( $message ) ;
} else if ( utils . isHeadlineMessage ( message ) ) {
// We want to show notifications for headline messages.
return true ;
}
var is _me = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) === converse . bare _jid ;
return ! converse . isOnlyChatStateNotification ( $message ) && ! is _me ;
} ;
converse . playSoundNotification = function ( $message ) {
/ * P l a y s a s o u n d t o n o t i f y t h a t a n e w m e s s a g e w a s r e c i e v e d .
* /
// XXX Eventually this can be refactored to use Notification's sound
// feature, but no browser currently supports it.
// https://developer.mozilla.org/en-US/docs/Web/API/notification/sound
var audio ;
if ( converse . play _sounds && typeof Audio !== "undefined" ) {
audio = new Audio ( converse . sounds _path + "msg_received.ogg" ) ;
if ( audio . canPlayType ( '/audio/ogg' ) ) {
audio . play ( ) ;
} else {
audio = new Audio ( converse . sounds _path + "msg_received.mp3" ) ;
audio . play ( ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
}
} ;
2016-02-26 18:28:18 +01:00
2016-06-20 21:11:43 +02:00
converse . areDesktopNotificationsEnabled = function ( ignore _hidden ) {
var enabled = converse . supports _html5 _notification &&
2016-04-01 15:33:14 +02:00
converse . show _desktop _notifications &&
Notification . permission === "granted" ;
2016-06-20 21:11:43 +02:00
if ( ignore _hidden ) {
2016-04-01 15:33:14 +02:00
return enabled ;
} else {
2016-06-20 21:11:43 +02:00
return enabled && converse . windowState === 'hidden' ;
2016-04-01 15:33:14 +02:00
}
} ;
converse . showMessageNotification = function ( $message ) {
/ * S h o w s a n H T M L 5 N o t i f i c a t i o n t o i n d i c a t e t h a t a n e w c h a t
* message was received .
* /
var n , title , contact _jid , roster _item ,
from _jid = $message . attr ( 'from' ) ;
if ( $message . attr ( 'type' ) === 'headline' || from _jid . indexOf ( '@' ) === - 1 ) {
// XXX: 2nd check is workaround for Prosody which doesn't
// give type "headline"
title = _ _ ( _ _ _ ( "Notification from %1$s" ) , from _jid ) ;
} else {
2016-05-03 17:37:10 +02:00
if ( $message . attr ( 'type' ) === 'groupchat' ) {
title = _ _ ( _ _ _ ( "%1$s says" ) , Strophe . getResourceFromJid ( from _jid ) ) ;
} else {
if ( typeof converse . roster === 'undefined' ) {
converse . log ( "Could not send notification, because roster is undefined" , "error" ) ;
return ;
}
contact _jid = Strophe . getBareJidFromJid ( $message . attr ( 'from' ) ) ;
roster _item = converse . roster . get ( contact _jid ) ;
title = _ _ ( _ _ _ ( "%1$s says" ) , roster _item . get ( 'fullname' ) ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
}
n = new Notification ( title , {
body : $message . children ( 'body' ) . text ( ) ,
lang : converse . i18n . locale _data . converse [ "" ] . lang ,
icon : converse . notification _icon
2016-03-19 14:33:31 +01:00
} ) ;
2016-04-01 15:33:14 +02:00
setTimeout ( n . close . bind ( n ) , 5000 ) ;
} ;
converse . showChatStateNotification = function ( contact ) {
/ * C r e a t e s a n H T M L 5 N o t i f i c a t i o n t o i n f o r m o f a c h a n g e i n a
* contact ' s chat state .
* /
if ( _ . contains ( converse . chatstate _notification _blacklist , contact . jid ) ) {
// Don't notify if the user is being ignored.
return ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
var chat _state = contact . chat _status ,
message = null ;
if ( chat _state === 'offline' ) {
message = _ _ ( 'has gone offline' ) ;
} else if ( chat _state === 'away' ) {
message = _ _ ( 'has gone away' ) ;
} else if ( ( chat _state === 'dnd' ) ) {
message = _ _ ( 'is busy' ) ;
} else if ( chat _state === 'online' ) {
message = _ _ ( 'has come online' ) ;
}
if ( message === null ) {
return ;
}
var n = new Notification ( contact . fullname , {
body : message ,
lang : converse . i18n . locale _data . converse [ "" ] . lang ,
icon : 'logo/conversejs.png'
} ) ;
setTimeout ( n . close . bind ( n ) , 5000 ) ;
} ;
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
converse . showContactRequestNotification = function ( contact ) {
var n = new Notification ( contact . fullname , {
body : _ _ ( 'wants to be your contact' ) ,
lang : converse . i18n . locale _data . converse [ "" ] . lang ,
icon : 'logo/conversejs.png'
} ) ;
setTimeout ( n . close . bind ( n ) , 5000 ) ;
} ;
2016-02-26 18:28:18 +01:00
2016-09-16 14:35:02 +02:00
converse . showFeedbackNotification = function ( data ) {
if ( data . klass === 'error' || data . klass === 'warn' ) {
var n = new Notification ( data . subject , {
body : data . message ,
lang : converse . i18n . locale _data . converse [ "" ] . lang ,
icon : 'logo/conversejs.png'
} ) ;
setTimeout ( n . close . bind ( n ) , 5000 ) ;
}
2016-04-01 15:33:14 +02:00
} ;
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
converse . handleChatStateNotification = function ( evt , contact ) {
/ * E v e n t h a n d l e r f o r o n ( ' c o n t a c t S t a t u s C h a n g e d ' ) .
* Will show an HTML5 notification to indicate that the chat
* status has changed .
* /
if ( converse . areDesktopNotificationsEnabled ( ) ) {
converse . showChatStateNotification ( contact ) ;
}
2016-02-26 18:28:18 +01:00
} ;
2016-04-01 15:33:14 +02:00
converse . handleMessageNotification = function ( evt , message ) {
/ * E v e n t h a n d l e r f o r t h e o n ( ' m e s s a g e ' ) e v e n t . W i l l c a l l m e t h o d s
* to play sounds and show HTML5 notifications .
* /
var $message = $ ( message ) ;
if ( ! converse . shouldNotifyOfMessage ( message ) ) {
return false ;
}
converse . playSoundNotification ( $message ) ;
if ( converse . areDesktopNotificationsEnabled ( ) ) {
converse . showMessageNotification ( $message ) ;
}
} ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
converse . handleContactRequestNotification = function ( evt , contact ) {
if ( converse . areDesktopNotificationsEnabled ( true ) ) {
converse . showContactRequestNotification ( contact ) ;
}
} ;
2016-02-26 18:28:18 +01:00
2016-04-01 15:33:14 +02:00
converse . handleFeedback = function ( evt , data ) {
2016-09-16 14:35:02 +02:00
if ( converse . areDesktopNotificationsEnabled ( true ) ) {
converse . showFeedbackNotification ( data ) ;
2016-02-26 18:28:18 +01:00
}
2016-04-01 15:33:14 +02:00
} ;
converse . requestPermission = function ( evt ) {
2016-06-20 21:11:43 +02:00
if ( converse . supports _html5 _notification &&
2016-04-01 15:33:14 +02:00
! _ . contains ( [ 'denied' , 'granted' ] , Notification . permission ) ) {
// Ask user to enable HTML5 notifications
Notification . requestPermission ( ) ;
}
} ;
2016-06-20 21:11:43 +02:00
converse . on ( 'pluginsInitialized' , function ( ) {
// We only register event handlers after all plugins are
// registered, because other plugins might override some of our
// handlers.
converse . on ( 'contactRequest' , converse . handleContactRequestNotification ) ;
converse . on ( 'contactStatusChanged' , converse . handleChatStateNotification ) ;
converse . on ( 'message' , converse . handleMessageNotification ) ;
converse . on ( 'feedback' , converse . handleFeedback ) ;
converse . on ( 'connected' , converse . requestPermission ) ;
} ) ;
2016-02-26 18:28:18 +01:00
}
} ) ;
} ) ) ;
2016-03-07 18:54:07 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-04-01 15:33:14 +02:00
/*global Backbone, define, window */
2016-03-07 18:54:07 +01:00
2016-04-01 15:33:14 +02:00
( function ( root , factory ) {
define ( "converse-minimize" , [
"converse-core" ,
"converse-api" ,
"converse-controlbox" ,
"converse-chatview" ,
"converse-muc"
] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
var $ = converse _api . env . jQuery ,
_ = converse _api . env . _ ,
b64 _sha1 = converse _api . env . b64 _sha1 ,
moment = converse _api . env . moment ,
utils = converse _api . env . utils ,
_ _ = utils . _ _ . bind ( converse ) ;
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-minimize' , {
2016-03-07 18:54:07 +01:00
2016-03-19 14:33:31 +01:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
2016-03-16 12:49:35 +01:00
2016-07-26 08:00:30 +02:00
_initialize : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . _initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
converse . minimized _chats = new converse . MinimizedChats ( {
model : converse . chatboxes
} ) ;
2016-07-26 08:00:30 +02:00
return this ;
2016-04-01 15:33:14 +02:00
} ,
registerGlobalEventHandlers : function ( ) {
$ ( window ) . on ( "resize" , _ . debounce ( function ( ev ) {
if ( converse . connection . connected ) {
converse . chatboxviews . trimChats ( ) ;
2016-03-07 18:54:07 +01:00
}
2016-04-01 15:33:14 +02:00
} , 200 ) ) ;
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . registerGlobalEventHandlers . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
} ,
2016-03-07 18:54:07 +01:00
2016-04-01 15:33:14 +02:00
wrappedChatBox : function ( chatbox ) {
/ * W r a p a c h a t b o x f o r o u t s i d e c o n s u m p t i o n ( i . e . s o t h a t i t c a n b e
* returned via the API .
* /
if ( ! chatbox ) { return ; }
2016-09-16 14:35:02 +02:00
var box = this . _ _super _ _ . wrappedChatBox . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
box . maximize = chatbox . maximize . bind ( chatbox ) ;
box . minimize = chatbox . minimize . bind ( chatbox ) ;
return box ;
} ,
2016-03-08 13:16:04 +01:00
2016-04-01 15:33:14 +02:00
ChatBox : {
initialize : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( this . get ( 'id' ) === 'controlbox' ) {
return ;
}
this . save ( {
'minimized' : this . get ( 'minimized' ) || false ,
'time_minimized' : this . get ( 'time_minimized' ) || moment ( ) ,
} ) ;
} ,
maximize : function ( ) {
this . save ( {
'minimized' : false ,
'time_opened' : moment ( ) . valueOf ( )
} ) ;
} ,
2016-03-08 13:16:04 +01:00
2016-04-01 15:33:14 +02:00
minimize : function ( ) {
this . save ( {
'minimized' : true ,
'time_minimized' : moment ( ) . format ( )
} ) ;
} ,
} ,
2016-03-07 18:54:07 +01:00
2016-04-01 15:33:14 +02:00
ChatBoxView : {
2016-03-19 14:33:31 +01:00
events : {
2016-04-01 15:33:14 +02:00
'click .toggle-chatbox-button' : 'minimize' ,
2016-03-19 14:33:31 +01:00
} ,
2016-03-07 18:54:07 +01:00
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
this . model . on ( 'change:minimized' , this . onMinimizedChanged , this ) ;
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-08 13:16:04 +01:00
2016-04-01 15:33:14 +02:00
afterShown : function ( ) {
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . afterShown . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( ! this . model . get ( 'minimized' ) ) {
converse . chatboxviews . trimChats ( this ) ;
}
2016-03-19 14:33:31 +01:00
} ,
2016-03-08 13:16:04 +01:00
2016-04-01 15:33:14 +02:00
shouldShowOnTextMessage : function ( ) {
return ! this . model . get ( 'minimized' ) &&
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . shouldShowOnTextMessage . apply ( this , arguments ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-07 18:54:07 +01:00
2016-04-01 15:33:14 +02:00
setChatBoxHeight : function ( height ) {
if ( ! this . model . get ( 'minimized' ) ) {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . setChatBoxHeight . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
}
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
setChatBoxWidth : function ( width ) {
if ( ! this . model . get ( 'minimized' ) ) {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . setChatBoxWidth . apply ( this , arguments ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
} ,
onMinimizedChanged : function ( item ) {
if ( item . get ( 'minimized' ) ) {
this . minimize ( ) ;
} else {
this . maximize ( ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
} ,
onMaximized : function ( ) {
converse . chatboxviews . trimChats ( this ) ;
utils . refreshWebkit ( ) ;
this . $content . scrollTop ( this . model . get ( 'scroll' ) ) ;
this . setChatState ( converse . ACTIVE ) . focus ( ) ;
2016-05-03 17:37:10 +02:00
this . scrollDown ( ) ;
2016-04-01 15:33:14 +02:00
converse . emit ( 'chatBoxMaximized' , this ) ;
} ,
onMinimized : function ( ) {
utils . refreshWebkit ( ) ;
converse . emit ( 'chatBoxMinimized' , this ) ;
} ,
maximize : function ( ) {
// Restores a minimized chat box
var chatboxviews = converse . chatboxviews ;
this . $el . insertAfter ( chatboxviews . get ( "controlbox" ) . $el )
. show ( 'fast' , this . onMaximized . bind ( this ) ) ;
return this ;
} ,
minimize : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
// save the scroll position to restore it on maximize
this . model . save ( { 'scroll' : this . $content . scrollTop ( ) } ) ;
this . setChatState ( converse . INACTIVE ) . model . minimize ( ) ;
this . $el . hide ( 'fast' , this . onMinimized . bind ( this ) ) ;
} ,
} ,
ChatRoomView : {
events : {
'click .toggle-chatbox-button' : 'minimize' ,
} ,
initialize : function ( ) {
this . model . on ( 'change:minimized' , function ( item ) {
if ( item . get ( 'minimized' ) ) {
this . hide ( ) ;
} else {
this . maximize ( ) ;
}
} , this ) ;
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( this . model . get ( 'minimized' ) ) {
this . hide ( ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return result ;
}
} ,
ChatBoxes : {
2016-05-03 17:37:10 +02:00
chatBoxMayBeShown : function ( chatbox ) {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . chatBoxMayBeShown . apply ( this , arguments ) &&
2016-04-01 15:33:14 +02:00
! chatbox . get ( 'minimized' ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-04-01 15:33:14 +02:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
ChatBoxViews : {
showChat : function ( attrs ) {
/ * F i n d t h e c h a t b o x a n d s h o w i t . I f i t d o e s n ' t e x i s t , c r e a t e i t .
2016-03-29 08:56:13 +02:00
* /
2016-09-16 14:35:02 +02:00
var chatbox = this . _ _super _ _ . showChat . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
if ( chatbox . get ( 'minimized' ) ) {
chatbox . maximize ( ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return chatbox ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
getChatBoxWidth : function ( view ) {
if ( ! view . model . get ( 'minimized' ) && view . $el . is ( ':visible' ) ) {
return view . $el . outerWidth ( true ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return 0 ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
getShownChats : function ( ) {
return this . filter ( function ( view ) {
2016-06-20 21:11:43 +02:00
// The controlbox can take a while to close,
// so we need to check its state. That's why we checked
// the 'closed' state.
return (
! view . model . get ( 'minimized' ) &&
! view . model . get ( 'closed' ) &&
view . $el . is ( ':visible' )
) ;
2016-04-01 15:33:14 +02:00
} ) ;
} ,
trimChats : function ( newchat ) {
/ * T h i s m e t h o d i s c a l l e d w h e n a n e w l y c r e a t e d c h a t b o x w i l l
* be shown .
2016-03-29 08:56:13 +02:00
*
2016-04-01 15:33:14 +02:00
* It checks whether there is enough space on the page to show
* another chat box . Otherwise it minimizes the oldest chat box
* to create space .
2016-03-29 08:56:13 +02:00
* /
2016-04-01 15:33:14 +02:00
var shown _chats = this . getShownChats ( ) ;
if ( converse . no _trimming || shown _chats . length <= 1 ) {
2016-03-19 14:33:31 +01:00
return ;
}
2016-04-01 15:33:14 +02:00
if ( this . getChatBoxWidth ( shown _chats [ 0 ] ) === $ ( 'body' ) . outerWidth ( true ) ) {
// If the chats shown are the same width as the body,
// then we're in responsive mode and the chats are
// fullscreen. In this case we don't trim.
return ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
var oldest _chat , boxes _width , view ,
$minimized = converse . minimized _chats . $el ,
minimized _width = _ . contains ( this . model . pluck ( 'minimized' ) , true ) ? $minimized . outerWidth ( true ) : 0 ,
new _id = newchat ? newchat . model . get ( 'id' ) : null ;
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
boxes _width = _ . reduce ( this . xget ( new _id ) , function ( memo , view ) {
return memo + this . getChatBoxWidth ( view ) ;
} . bind ( this ) , newchat ? newchat . $el . outerWidth ( true ) : 0 ) ;
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
if ( ( minimized _width + boxes _width ) > $ ( 'body' ) . outerWidth ( true ) ) {
oldest _chat = this . getOldestMaximizedChat ( [ new _id ] ) ;
if ( oldest _chat ) {
// We hide the chat immediately, because waiting
// for the event to fire (and letting the
// ChatBoxView hide it then) causes race
// conditions.
view = this . get ( oldest _chat . get ( 'id' ) ) ;
if ( view ) {
view . $el . hide ( ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
oldest _chat . minimize ( ) ;
}
2016-03-19 14:33:31 +01:00
}
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
getOldestMaximizedChat : function ( exclude _ids ) {
// Get oldest view (if its id is not excluded)
exclude _ids . push ( 'controlbox' ) ;
var i = 0 ;
var model = this . model . sort ( ) . at ( i ) ;
while ( _ . contains ( exclude _ids , model . get ( 'id' ) ) ||
model . get ( 'minimized' ) === true ) {
i ++ ;
model = this . model . at ( i ) ;
if ( ! model ) {
return null ;
}
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return model ;
}
}
} ,
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
this . updateSettings ( {
no _trimming : false , // Set to true for phantomjs tests (where browser apparently has no width)
} ) ;
converse . MinimizedChatBoxView = Backbone . View . extend ( {
tagName : 'div' ,
className : 'chat-head' ,
events : {
'click .close-chatbox-button' : 'close' ,
'click .restore-chat' : 'restore'
} ,
initialize : function ( ) {
this . model . messages . on ( 'add' , function ( m ) {
if ( m . get ( 'message' ) ) {
this . updateUnreadMessagesCounter ( ) ;
}
} , this ) ;
this . model . on ( 'change:minimized' , this . clearUnreadMessagesCounter , this ) ;
// OTR stuff, doesn't require this module to depend on OTR.
this . model . on ( 'showReceivedOTRMessage' , this . updateUnreadMessagesCounter , this ) ;
this . model . on ( 'showSentOTRMessage' , this . updateUnreadMessagesCounter , this ) ;
} ,
render : function ( ) {
var data = _ . extend (
this . model . toJSON ( ) ,
{ 'tooltip' : _ _ ( 'Click to restore this chat' ) }
) ;
if ( this . model . get ( 'type' ) === 'chatroom' ) {
data . title = this . model . get ( 'name' ) ;
this . $el . addClass ( 'chat-head-chatroom' ) ;
2016-03-19 14:33:31 +01:00
} else {
2016-04-01 15:33:14 +02:00
data . title = this . model . get ( 'fullname' ) ;
this . $el . addClass ( 'chat-head-chatbox' ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return this . $el . html ( converse . templates . trimmed _chat ( data ) ) ;
} ,
clearUnreadMessagesCounter : function ( ) {
this . model . set ( { 'num_unread' : 0 } ) ;
this . render ( ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
updateUnreadMessagesCounter : function ( ) {
this . model . set ( { 'num_unread' : this . model . get ( 'num_unread' ) + 1 } ) ;
this . render ( ) ;
} ,
close : function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
this . remove ( ) ;
var view = converse . chatboxviews . get ( this . model . get ( 'id' ) ) ;
if ( view ) {
// This will call model.destroy(), removing it from the
// collection and will also emit 'chatBoxClosed'
view . close ( ) ;
2016-03-19 14:33:31 +01:00
} else {
2016-04-01 15:33:14 +02:00
this . model . destroy ( ) ;
converse . emit ( 'chatBoxClosed' , this ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return this ;
} ,
restore : _ . debounce ( function ( ev ) {
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
this . model . messages . off ( 'add' , null , this ) ;
this . remove ( ) ;
this . model . maximize ( ) ;
} , 200 , true )
} ) ;
converse . MinimizedChats = Backbone . Overview . extend ( {
el : "#minimized-chats" ,
events : {
"click #toggle-minimized-chats" : "toggle"
} ,
initialize : function ( ) {
this . initToggle ( ) ;
this . model . on ( "add" , this . onChanged , this ) ;
this . model . on ( "destroy" , this . removeChat , this ) ;
this . model . on ( "change:minimized" , this . onChanged , this ) ;
this . model . on ( 'change:num_unread' , this . updateUnreadMessagesCounter , this ) ;
} ,
tearDown : function ( ) {
this . model . off ( "add" , this . onChanged ) ;
this . model . off ( "destroy" , this . removeChat ) ;
this . model . off ( "change:minimized" , this . onChanged ) ;
this . model . off ( 'change:num_unread' , this . updateUnreadMessagesCounter ) ;
return this ;
} ,
initToggle : function ( ) {
this . toggleview = new converse . MinimizedChatsToggleView ( {
model : new converse . MinimizedChatsToggle ( )
2016-03-19 14:33:31 +01:00
} ) ;
2016-04-01 15:33:14 +02:00
var id = b64 _sha1 ( 'converse.minchatstoggle' + converse . bare _jid ) ;
this . toggleview . model . id = id ; // Appears to be necessary for backbone.browserStorage
this . toggleview . model . browserStorage = new Backbone . BrowserStorage [ converse . storage ] ( id ) ;
this . toggleview . model . fetch ( ) ;
} ,
render : function ( ) {
if ( this . keys ( ) . length === 0 ) {
2016-05-03 17:37:10 +02:00
this . $el . hide ( ) ;
converse . chatboxviews . trimChats . bind ( converse . chatboxviews ) ;
2016-04-01 15:33:14 +02:00
} else if ( this . keys ( ) . length === 1 && ! this . $el . is ( ':visible' ) ) {
this . $el . show ( 'fast' , converse . chatboxviews . trimChats . bind ( converse . chatboxviews ) ) ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
return this . $el ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
toggle : function ( ev ) {
2016-03-19 14:33:31 +01:00
if ( ev && ev . preventDefault ) { ev . preventDefault ( ) ; }
2016-04-01 15:33:14 +02:00
this . toggleview . model . save ( { 'collapsed' : ! this . toggleview . model . get ( 'collapsed' ) } ) ;
this . $ ( '.minimized-chats-flyout' ) . toggle ( ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
onChanged : function ( item ) {
if ( item . get ( 'id' ) === 'controlbox' ) {
// The ControlBox has it's own minimize toggle
2016-03-16 12:49:35 +01:00
return ;
}
2016-04-01 15:33:14 +02:00
if ( item . get ( 'minimized' ) ) {
this . addChat ( item ) ;
} else if ( this . get ( item . get ( 'id' ) ) ) {
this . removeChat ( item ) ;
2016-03-19 14:33:31 +01:00
}
2016-03-16 12:49:35 +01:00
} ,
2016-04-01 15:33:14 +02:00
addChat : function ( item ) {
var existing = this . get ( item . get ( 'id' ) ) ;
if ( existing && existing . $el . parent ( ) . length !== 0 ) {
return ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
var view = new converse . MinimizedChatBoxView ( { model : item } ) ;
this . $ ( '.minimized-chats-flyout' ) . append ( view . render ( ) ) ;
this . add ( item . get ( 'id' ) , view ) ;
this . toggleview . model . set ( { 'num_minimized' : this . keys ( ) . length } ) ;
this . render ( ) ;
2016-03-16 12:49:35 +01:00
} ,
2016-04-01 15:33:14 +02:00
removeChat : function ( item ) {
this . remove ( item . get ( 'id' ) ) ;
this . toggleview . model . set ( { 'num_minimized' : this . keys ( ) . length } ) ;
this . render ( ) ;
2016-03-16 12:49:35 +01:00
} ,
2016-04-01 15:33:14 +02:00
updateUnreadMessagesCounter : function ( ) {
var ls = this . model . pluck ( 'num_unread' ) ,
count = 0 , i ;
for ( i = 0 ; i < ls . length ; i ++ ) { count += ls [ i ] ; }
this . toggleview . model . set ( { 'num_unread' : count } ) ;
this . render ( ) ;
}
} ) ;
converse . MinimizedChatsToggle = Backbone . Model . extend ( {
initialize : function ( ) {
this . set ( {
'collapsed' : this . get ( 'collapsed' ) || false ,
'num_minimized' : this . get ( 'num_minimized' ) || 0 ,
'num_unread' : this . get ( 'num_unread' ) || 0
} ) ;
}
} ) ;
converse . MinimizedChatsToggleView = Backbone . View . extend ( {
el : '#toggle-minimized-chats' ,
initialize : function ( ) {
this . model . on ( 'change:num_minimized' , this . render , this ) ;
this . model . on ( 'change:num_unread' , this . render , this ) ;
this . $flyout = this . $el . siblings ( '.minimized-chats-flyout' ) ;
2016-03-19 14:33:31 +01:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
render : function ( ) {
this . $el . html ( converse . templates . toggle _chats (
_ . extend ( this . model . toJSON ( ) , {
'Minimized' : _ _ ( 'Minimized' )
} )
) ) ;
if ( this . model . get ( 'collapsed' ) ) {
this . $flyout . hide ( ) ;
2016-03-19 14:33:31 +01:00
} else {
2016-04-01 15:33:14 +02:00
this . $flyout . show ( ) ;
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
return this . $el ;
}
} ) ;
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
var renderMinimizeButton = function ( evt , view ) {
// Inserts a "minimize" button in the chatview's header
var $el = view . $el . find ( '.toggle-chatbox-button' ) ;
var $new _el = converse . templates . chatbox _minimize (
_ . extend ( { info _minimize : _ _ ( 'Minimize this chat box' ) } )
) ;
if ( $el . length ) {
$el . replaceWith ( $new _el ) ;
} else {
view . $el . find ( '.close-chatbox-button' ) . after ( $new _el ) ;
}
} ;
converse . on ( 'chatBoxOpened' , renderMinimizeButton ) ;
converse . on ( 'chatRoomOpened' , renderMinimizeButton ) ;
converse . on ( 'controlBoxOpened' , function ( evt , chatbox ) {
// Wrapped in anon method because at scan time, chatboxviews
// attr not set yet.
if ( converse . connection . connected ) {
converse . chatboxviews . trimChats ( chatbox ) ;
2016-03-19 14:33:31 +01:00
}
} ) ;
}
} ) ;
} ) ) ;
2016-03-16 12:49:35 +01:00
2016-03-19 14:33:31 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-04-01 15:33:14 +02:00
/*global define, window */
2016-03-16 12:49:35 +01:00
2016-03-19 14:33:31 +01:00
( function ( root , factory ) {
2016-04-01 15:33:14 +02:00
define ( "converse-dragresize" , [
"converse-core" ,
"converse-api" ,
"converse-chatview" ,
"converse-muc" , // XXX: would like to remove this
"converse-controlbox"
2016-03-19 14:33:31 +01:00
] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
2016-04-01 15:33:14 +02:00
var $ = converse _api . env . jQuery ,
_ = converse _api . env . _ ;
2016-03-16 12:49:35 +01:00
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-dragresize' , {
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
2016-06-20 21:11:43 +02:00
2016-04-01 15:33:14 +02:00
registerGlobalEventHandlers : function ( ) {
$ ( document ) . on ( 'mousemove' , function ( ev ) {
if ( ! this . resizing || ! this . allow _dragresize ) { return true ; }
ev . preventDefault ( ) ;
this . resizing . chatbox . resizeChatBox ( ev ) ;
} . bind ( this ) ) ;
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
$ ( document ) . on ( 'mouseup' , function ( ev ) {
if ( ! this . resizing || ! this . allow _dragresize ) { return true ; }
ev . preventDefault ( ) ;
var height = this . applyDragResistance (
this . resizing . chatbox . height ,
this . resizing . chatbox . model . get ( 'default_height' )
) ;
var width = this . applyDragResistance (
this . resizing . chatbox . width ,
this . resizing . chatbox . model . get ( 'default_width' )
) ;
if ( this . connection . connected ) {
this . resizing . chatbox . model . save ( { 'height' : height } ) ;
this . resizing . chatbox . model . save ( { 'width' : width } ) ;
} else {
this . resizing . chatbox . model . set ( { 'height' : height } ) ;
this . resizing . chatbox . model . set ( { 'width' : width } ) ;
}
this . resizing = null ;
} . bind ( this ) ) ;
2016-03-16 12:49:35 +01:00
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . registerGlobalEventHandlers . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
ChatBox : {
2016-05-24 10:23:19 +02:00
initialize : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . initialize . apply ( this , arguments ) ,
2016-04-01 15:33:14 +02:00
height = this . get ( 'height' ) , width = this . get ( 'width' ) ,
save = this . get ( 'id' ) === 'controlbox' ? this . set . bind ( this ) : this . save . bind ( this ) ;
save ( {
'height' : converse . applyDragResistance ( height , this . get ( 'default_height' ) ) ,
'width' : converse . applyDragResistance ( width , this . get ( 'default_width' ) ) ,
2016-03-19 14:33:31 +01:00
} ) ;
2016-04-01 15:33:14 +02:00
return result ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
ChatBoxView : {
events : {
'mousedown .dragresize-top' : 'onStartVerticalResize' ,
'mousedown .dragresize-left' : 'onStartHorizontalResize' ,
'mousedown .dragresize-topleft' : 'onStartDiagonalResize'
} ,
2016-03-29 08:56:13 +02:00
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
$ ( window ) . on ( 'resize' , _ . debounce ( this . setDimensions . bind ( this ) , 100 ) ) ;
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
render : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . render . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
this . setWidth ( ) ;
return result ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
setWidth : function ( ) {
// If a custom width is applied (due to drag-resizing),
// then we need to set the width of the .chatbox element as well.
if ( this . model . get ( 'width' ) ) {
this . $el . css ( 'width' , this . model . get ( 'width' ) ) ;
}
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
_show : function ( ) {
this . initDragResize ( ) . setDimensions ( ) ;
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . _show . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
} ,
initDragResize : function ( ) {
/ * D e t e r m i n e a n d s t o r e t h e d e f a u l t b o x s i z e .
* We need this information for the drag - resizing feature .
* /
var $flyout = this . $el . find ( '.box-flyout' ) ;
if ( typeof this . model . get ( 'height' ) === 'undefined' ) {
var height = $flyout . height ( ) ;
var width = $flyout . width ( ) ;
this . model . set ( 'height' , height ) ;
this . model . set ( 'default_height' , height ) ;
this . model . set ( 'width' , width ) ;
this . model . set ( 'default_width' , width ) ;
}
var min _width = $flyout . css ( 'min-width' ) ;
var min _height = $flyout . css ( 'min-height' ) ;
this . model . set ( 'min_width' , min _width . endsWith ( 'px' ) ? Number ( min _width . replace ( /px$/ , '' ) ) : 0 ) ;
this . model . set ( 'min_height' , min _height . endsWith ( 'px' ) ? Number ( min _height . replace ( /px$/ , '' ) ) : 0 ) ;
// Initialize last known mouse position
this . prev _pageY = 0 ;
this . prev _pageX = 0 ;
if ( converse . connection . connected ) {
this . height = this . model . get ( 'height' ) ;
this . width = this . model . get ( 'width' ) ;
}
return this ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
setDimensions : function ( ) {
// Make sure the chat box has the right height and width.
this . adjustToViewport ( ) ;
this . setChatBoxHeight ( this . model . get ( 'height' ) ) ;
this . setChatBoxWidth ( this . model . get ( 'width' ) ) ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
setChatBoxHeight : function ( height ) {
if ( height ) {
height = converse . applyDragResistance ( height , this . model . get ( 'default_height' ) ) + 'px' ;
} else {
height = "" ;
}
this . $el . children ( '.box-flyout' ) [ 0 ] . style . height = height ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
setChatBoxWidth : function ( width ) {
if ( width ) {
width = converse . applyDragResistance ( width , this . model . get ( 'default_width' ) ) + 'px' ;
} else {
width = "" ;
}
this . $el [ 0 ] . style . width = width ;
this . $el . children ( '.box-flyout' ) [ 0 ] . style . width = width ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
adjustToViewport : function ( ) {
/ * E v e n t h a n d l e r c a l l e d w h e n v i e w p o r t g e t s r e s i z e d . W e r e m o v e
* custom width / height from chat boxes .
* /
var viewport _width = Math . max ( document . documentElement . clientWidth , window . innerWidth || 0 ) ;
var viewport _height = Math . max ( document . documentElement . clientHeight , window . innerHeight || 0 ) ;
if ( viewport _width <= 480 ) {
this . model . set ( 'height' , undefined ) ;
this . model . set ( 'width' , undefined ) ;
} else if ( viewport _width <= this . model . get ( 'width' ) ) {
this . model . set ( 'width' , undefined ) ;
} else if ( viewport _height <= this . model . get ( 'height' ) ) {
this . model . set ( 'height' , undefined ) ;
}
} ,
onStartVerticalResize : function ( ev ) {
if ( ! converse . allow _dragresize ) { return true ; }
// Record element attributes for mouseMove().
this . height = this . $el . children ( '.box-flyout' ) . height ( ) ;
converse . resizing = {
'chatbox' : this ,
'direction' : 'top'
} ;
this . prev _pageY = ev . pageY ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
onStartHorizontalResize : function ( ev ) {
if ( ! converse . allow _dragresize ) { return true ; }
this . width = this . $el . children ( '.box-flyout' ) . width ( ) ;
converse . resizing = {
'chatbox' : this ,
'direction' : 'left'
} ;
this . prev _pageX = ev . pageX ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
onStartDiagonalResize : function ( ev ) {
this . onStartHorizontalResize ( ev ) ;
this . onStartVerticalResize ( ev ) ;
converse . resizing . direction = 'topleft' ;
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
resizeChatBox : function ( ev ) {
var diff ;
if ( converse . resizing . direction . indexOf ( 'top' ) === 0 ) {
diff = ev . pageY - this . prev _pageY ;
if ( diff ) {
this . height = ( ( this . height - diff ) > ( this . model . get ( 'min_height' ) || 0 ) ) ? ( this . height - diff ) : this . model . get ( 'min_height' ) ;
this . prev _pageY = ev . pageY ;
this . setChatBoxHeight ( this . height ) ;
}
}
if ( converse . resizing . direction . indexOf ( 'left' ) !== - 1 ) {
diff = this . prev _pageX - ev . pageX ;
if ( diff ) {
this . width = ( ( this . width + diff ) > ( this . model . get ( 'min_width' ) || 0 ) ) ? ( this . width + diff ) : this . model . get ( 'min_width' ) ;
this . prev _pageX = ev . pageX ;
this . setChatBoxWidth ( this . width ) ;
}
2016-03-19 14:33:31 +01:00
}
}
2016-04-01 15:33:14 +02:00
} ,
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
ControlBoxView : {
events : {
'mousedown .dragresize-top' : 'onStartVerticalResize' ,
'mousedown .dragresize-left' : 'onStartHorizontalResize' ,
'mousedown .dragresize-topleft' : 'onStartDiagonalResize'
} ,
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
$ ( window ) . on ( 'resize' , _ . debounce ( this . setDimensions . bind ( this ) , 100 ) ) ;
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
} ,
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
renderLoginPanel : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . renderLoginPanel . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
this . initDragResize ( ) . setDimensions ( ) ;
return result ;
} ,
renderContactsPanel : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . renderContactsPanel . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
this . initDragResize ( ) . setDimensions ( ) ;
return result ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
} ,
2016-03-19 14:33:31 +01:00
2016-04-01 15:33:14 +02:00
ChatRoomView : {
events : {
'mousedown .dragresize-top' : 'onStartVerticalResize' ,
'mousedown .dragresize-left' : 'onStartHorizontalResize' ,
'mousedown .dragresize-topleft' : 'onStartDiagonalResize'
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
$ ( window ) . on ( 'resize' , _ . debounce ( this . setDimensions . bind ( this ) , 100 ) ) ;
2016-09-16 14:35:02 +02:00
this . _ _super _ _ . initialize . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
render : function ( ) {
2016-09-16 14:35:02 +02:00
var result = this . _ _super _ _ . render . apply ( this , arguments ) ;
2016-04-01 15:33:14 +02:00
this . setWidth ( ) ;
return result ;
2016-03-19 14:33:31 +01:00
}
2016-04-01 15:33:14 +02:00
}
} ,
2016-03-16 12:49:35 +01:00
2016-04-01 15:33:14 +02:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
var converse = this . converse ;
this . updateSettings ( {
allow _dragresize : true ,
} ) ;
converse . applyDragResistance = function ( value , default _value ) {
/ * T h i s m e t h o d a p p l i e s s o m e r e s i s t a n c e a r o u n d t h e
* default _value . If value is close enough to
* default _value , then default _value is returned instead .
* /
if ( typeof value === 'undefined' ) {
return undefined ;
} else if ( typeof default _value === 'undefined' ) {
return value ;
}
var resistance = 10 ;
if ( ( value !== default _value ) &&
( Math . abs ( value - default _value ) < resistance ) ) {
return default _value ;
2016-03-16 12:49:35 +01:00
}
2016-04-01 15:33:14 +02:00
return value ;
2016-03-19 14:33:31 +01:00
} ;
2016-03-16 12:49:35 +01:00
}
} ) ;
} ) ) ;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2016, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-03-29 08:56:13 +02:00
/*global define, window */
2016-03-16 12:49:35 +01:00
( function ( root , factory ) {
define ( "converse-headline" , [
"converse-core" ,
"converse-api" ,
"converse-chatview"
] , factory ) ;
} ( this , function ( converse , converse _api ) {
"use strict" ;
2016-03-29 08:56:13 +02:00
var $ = converse _api . env . jQuery ,
_ = converse _api . env . _ ,
utils = converse _api . env . utils ,
_ _ = utils . _ _ . bind ( converse ) ;
var onHeadlineMessage = function ( message ) {
/ * H a n d l e r m e t h o d f o r a l l i n c o m i n g m e s s a g e s o f t y p e " h e a d l i n e " .
* /
var $message = $ ( message ) ,
from _jid = $message . attr ( 'from' ) ;
if ( utils . isHeadlineMessage ( message ) ) {
converse . chatboxes . create ( {
'id' : from _jid ,
'jid' : from _jid ,
'fullname' : from _jid ,
'type' : 'headline'
2016-06-20 21:11:43 +02:00
} ) . createMessage ( $message , undefined , message ) ;
2016-03-29 08:56:13 +02:00
converse . emit ( 'message' , message ) ;
}
return true ;
} ;
2016-03-16 12:49:35 +01:00
2016-06-20 21:11:43 +02:00
converse _api . plugins . add ( 'converse-headline' , {
2016-03-16 12:49:35 +01:00
2016-03-29 08:56:13 +02:00
overrides : {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
2016-06-20 21:11:43 +02:00
2016-03-29 08:56:13 +02:00
ChatBoxViews : {
onChatBoxAdded : function ( item ) {
var view = this . get ( item . get ( 'id' ) ) ;
if ( ! view && item . get ( 'type' ) === 'headline' ) {
view = new converse . HeadlinesBoxView ( { model : item } ) ;
this . add ( item . get ( 'id' ) , view ) ;
return view ;
} else {
2016-09-16 14:35:02 +02:00
return this . _ _super _ _ . onChatBoxAdded . apply ( this , arguments ) ;
2016-03-29 08:56:13 +02:00
}
}
}
} ,
2016-03-16 12:49:35 +01:00
initialize : function ( ) {
/ * T h e i n i t i a l i z e f u n c t i o n g e t s c a l l e d a s s o o n a s t h e p l u g i n i s
* loaded by converse . js ' s plugin machinery .
* /
2016-03-29 08:56:13 +02:00
converse . HeadlinesBoxView = converse . ChatBoxView . extend ( {
className : 'chatbox headlines' ,
events : {
'click .close-chatbox-button' : 'close' ,
'click .toggle-chatbox-button' : 'minimize' ,
'keypress textarea.chat-textarea' : 'keyPressed' ,
'mousedown .dragresize-top' : 'onStartVerticalResize' ,
'mousedown .dragresize-left' : 'onStartHorizontalResize' ,
'mousedown .dragresize-topleft' : 'onStartDiagonalResize'
} ,
initialize : function ( ) {
2016-06-20 21:11:43 +02:00
if ( typeof this . setDimensions !== "undefined" ) {
// setDimensions is defined for dragresize
$ ( window ) . on ( 'resize' , _ . debounce ( this . setDimensions . bind ( this ) , 100 ) ) ;
}
2016-04-01 15:33:14 +02:00
this . disable _mam = true ; // Don't do MAM queries for this box
2016-03-29 08:56:13 +02:00
this . model . messages . on ( 'add' , this . onMessageAdded , this ) ;
this . model . on ( 'show' , this . show , this ) ;
this . model . on ( 'destroy' , this . hide , this ) ;
this . model . on ( 'change:minimized' , this . onMinimizedChanged , this ) ;
2016-07-26 08:00:30 +02:00
this . render ( ) . fetchMessages ( ) . insertIntoDOM ( ) . hide ( ) ;
2016-03-29 08:56:13 +02:00
converse . emit ( 'chatBoxInitialized' , this ) ;
} ,
render : function ( ) {
this . $el . attr ( 'id' , this . model . get ( 'box_id' ) )
. html ( converse . templates . chatbox (
_ . extend ( this . model . toJSON ( ) , {
show _toolbar : converse . show _toolbar ,
show _textarea : false ,
title : this . model . get ( 'fullname' ) ,
2016-06-20 21:11:43 +02:00
unread _msgs : _ _ ( 'You have unread messages' ) ,
2016-03-29 08:56:13 +02:00
info _close : _ _ ( 'Close this box' ) ,
info _minimize : _ _ ( 'Minimize this box' ) ,
label _personal _message : ''
}
)
)
) ;
2016-06-20 21:11:43 +02:00
if ( typeof this . setWidth !== "undefined" ) {
// setWidth is defined for dragresize
$ ( window ) . on ( 'resize' , _ . debounce ( this . setWidth . bind ( this ) , 100 ) ) ;
}
2016-03-29 08:56:13 +02:00
this . $content = this . $el . find ( '.chat-content' ) ;
converse . emit ( 'chatBoxOpened' , this ) ;
window . setTimeout ( utils . refreshWebkit , 50 ) ;
return this ;
2016-04-01 15:33:14 +02:00
}
2016-03-29 08:56:13 +02:00
} ) ;
var registerHeadlineHandler = function ( ) {
converse . connection . addHandler (
onHeadlineMessage , null , 'message' ) ;
} ;
converse . on ( 'connected' , registerHeadlineHandler ) ;
converse . on ( 'reconnected' , registerHeadlineHandler ) ;
2016-03-07 18:54:07 +01:00
}
} ) ;
} ) ) ;
2016-02-28 10:00:22 +01:00
/ * C o n v e r s e . j s c o m p o n e n t s c o n f i g u r a t i o n
2016-02-26 18:28:18 +01:00
*
* This file is used to tell require . js which components ( or plugins ) to load
* when it generates a build .
* /
2016-07-28 18:06:31 +02:00
2016-03-07 18:54:07 +01:00
if ( typeof define !== 'undefined' ) {
/* When running tests, define is not defined. */
define ( "converse" , [
2016-03-19 14:33:31 +01:00
"jquery" ,
2016-03-07 18:54:07 +01:00
"converse-api" ,
2016-02-26 18:28:18 +01:00
2016-03-07 18:54:07 +01:00
/ * S T A R T : R e m o v a b l e c o m p o n e n t s
* -- -- -- -- -- -- -- -- -- --
* Any of the following components may be removed if they ' re not needed .
* /
"locales" , // Translations for converse.js. This line can be removed
// to remove *all* translations, or you can modify the
// file src/locales.js to include only those
// translations that you care about.
2016-03-16 12:49:35 +01:00
"converse-chatview" , // Renders standalone chat boxes for single user chat
2016-06-20 21:11:43 +02:00
"converse-controlbox" , // The control box
2016-03-16 12:49:35 +01:00
"converse-mam" , // XEP-0313 Message Archive Management
2016-03-07 18:54:07 +01:00
"converse-muc" , // XEP-0045 Multi-user chat
2016-03-16 12:49:35 +01:00
"converse-vcard" , // XEP-0054 VCard-temp
2016-03-07 18:54:07 +01:00
"converse-otr" , // Off-the-record encryption for one-on-one messages
"converse-register" , // XEP-0077 In-band registration
"converse-ping" , // XEP-0199 XMPP Ping
"converse-notification" , // HTML5 Notifications
2016-03-16 12:49:35 +01:00
"converse-minimize" , // Allows chat boxes to be minimized
2016-04-01 15:33:14 +02:00
"converse-dragresize" , // Allows chat boxes to be resized by dragging them
2016-03-16 12:49:35 +01:00
"converse-headline" , // Support for headline messages
2016-03-07 18:54:07 +01:00
/* END: Removable components */
2016-03-19 14:33:31 +01:00
] , function ( $ , converse _api ) {
2016-03-07 18:54:07 +01:00
window . converse = converse _api ;
2016-03-19 14:33:31 +01:00
$ ( window ) . trigger ( 'converse-loaded' , converse _api ) ;
2016-03-07 18:54:07 +01:00
return converse _api ;
} ) ;
}
;
2016-02-26 18:28:18 +01:00
require ( [ "converse" ] ) ;
2016-03-07 18:54:07 +01:00
/*global jQuery */
2016-02-26 18:28:18 +01:00
define ( 'jquery' , [ ] , function ( ) { return jQuery ; } ) ;
2016-03-07 18:54:07 +01:00
define ( 'jquery-private' , [ ] , function ( ) { return jQuery ; } ) ;
/*global jQuery, _, moment, Strophe, $build, $iq, $msg, $pres, SHA1, Base64, MD5, DSA, OTR */
2016-02-26 18:28:18 +01:00
define ( 'jquery.browser' , [ ] , function ( ) { return jQuery ; } ) ;
define ( 'typeahead' , [ ] , function ( ) { return jQuery ; } ) ;
define ( 'underscore' , [ ] , function ( ) { return _ ; } ) ;
define ( 'moment_with_locales' , [ ] , function ( ) { return moment ; } ) ;
define ( 'strophe' , [ ] , function ( ) {
return {
'Strophe' : Strophe ,
'$build' : $build ,
'$iq' : $iq ,
'$msg' : $msg ,
'$pres' : $pres ,
'SHA1' : SHA1 ,
'Base64' : Base64 ,
'MD5' : MD5 ,
'b64_hmac_sha1' : SHA1 . b64 _hmac _sha1 ,
'b64_sha1' : SHA1 . b64 _sha1 ,
'str_hmac_sha1' : SHA1 . str _hmac _sha1 ,
'str_sha1' : SHA1 . str _sha1
} ;
} ) ;
2016-02-28 10:00:22 +01:00
var strophePlugin = function ( ) { return Strophe ; } ;
var emptyFunction = function ( ) { } ;
define ( 'strophe.disco' , [ 'strophe' ] , strophePlugin ) ;
define ( 'strophe.ping' , [ 'strophe' ] , strophePlugin ) ;
define ( 'strophe.rsm' , [ 'strophe' ] , strophePlugin ) ;
define ( 'strophe.vcard' , [ 'strophe' ] , strophePlugin ) ;
define ( 'backbone' , [ ] , emptyFunction ) ;
define ( 'backbone.browserStorage' , [ 'backbone' ] , emptyFunction ) ;
define ( 'backbone.overview' , [ 'backbone' ] , emptyFunction ) ;
2016-02-26 18:28:18 +01:00
define ( 'otr' , [ ] , function ( ) { return { 'DSA' : DSA , 'OTR' : OTR } ; } ) ;
2016-02-28 10:00:22 +01:00
define ( "locales" , [ ] , emptyFunction ) ;