1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/jappix_ynh.git synced 2024-09-03 19:26:19 +02:00
jappix_ynh/source/store/cache/4c8c9d7652050462941d708c6e94844e_plain.cache
2014-04-08 20:32:22 +02:00

37142 lines
993 KiB
Text

/*
Jappix - An open social platform
These are the origin JS script for Jappix
-------------------------------------------------
License: dual-licensed under AGPL and MPLv2
Author: Valérian Saliou
Last revision: 13/06/13
*/
// Checks if the URL passed has the same origin than Jappix itself
function isSameOrigin(url) {
/* Source: http://stackoverflow.com/questions/9404793/check-if-same-origin-policy-applies */
var loc = window.location,
a = document.createElement('a');
a.href = url;
return (!a.hostname || (a.hostname == loc.hostname)) &&
(!a.port || (a.port == loc.port)) &&
(!a.protocol || (a.protocol == loc.protocol));
}
// jXHR.js (JSON-P XHR)
// v0.1 (c) Kyle Simpson
// License: MIT
// modified by gueron Jonathan to work with strophe lib
// for http://www.iadvize.com
(function(global){
var SETTIMEOUT = global.setTimeout, // for better compression
doc = global.document,
callback_counter = 0;
global.jXHR = function() {
var script_url,
script_loaded,
jsonp_callback,
scriptElem,
publicAPI = null;
function removeScript() { try { scriptElem.parentNode.removeChild(scriptElem); } catch (err) { } }
function reset() {
script_loaded = false;
script_url = "";
removeScript();
scriptElem = null;
fireReadyStateChange(0);
}
function ThrowError(msg) {
try {
publicAPI.onerror.call(publicAPI,msg,script_url);
} catch (err) {
//throw new Error(msg);
}
}
function handleScriptLoad() {
if ((this.readyState && this.readyState!=="complete" && this.readyState!=="loaded") || script_loaded) { return; }
this.onload = this.onreadystatechange = null; // prevent memory leak
script_loaded = true;
if (publicAPI.readyState !== 4) ThrowError("handleScriptLoad: Script failed to load ["+script_url+"].");
removeScript();
}
function parseXMLString(xmlStr) {
var xmlDoc = null;
if(window.DOMParser) {
var parser = new DOMParser();
xmlDoc = parser.parseFromString(xmlStr,"text/xml");
}
else {
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.loadXML(xmlStr);
}
return xmlDoc;
}
function fireReadyStateChange(rs,args) {
args = args || [];
publicAPI.readyState = rs;
if (rs == 4) {
publicAPI.responseText = args[0].reply;
publicAPI.responseXML = parseXMLString(args[0].reply);
}
if (typeof publicAPI.onreadystatechange === "function") publicAPI.onreadystatechange.apply(publicAPI,args);
}
publicAPI = {
onerror:null,
onreadystatechange:null,
readyState:0,
status:200,
responseBody: null,
responseText: null,
responseXML: null,
open:function(method,url){
reset();
var internal_callback = "cb"+(callback_counter++);
(function(icb){
global.jXHR[icb] = function() {
try { fireReadyStateChange.call(publicAPI,4,arguments); }
catch(err) {
publicAPI.readyState = -1;
ThrowError("Script failed to run ["+script_url+"].");
}
global.jXHR[icb] = null;
};
})(internal_callback);
script_url = url + '?callback=?jXHR&data=';
script_url = script_url.replace(/=\?jXHR/,"=jXHR."+internal_callback);
fireReadyStateChange(1);
},
send:function(data){
script_url = script_url + encodeURIComponent(data);
SETTIMEOUT(function(){
scriptElem = doc.createElement("script");
scriptElem.setAttribute("type","text/javascript");
scriptElem.onload = scriptElem.onreadystatechange = function(){handleScriptLoad.call(scriptElem);};
scriptElem.setAttribute("src",script_url);
doc.getElementsByTagName("head")[0].appendChild(scriptElem);
},0);
fireReadyStateChange(2);
},
abort:function(){},
setRequestHeader:function(){}, // noop
getResponseHeader:function(){return "";}, // basically noop
getAllResponseHeaders:function(){return [];} // ditto
};
reset();
return publicAPI;
};
})(window);
/*!
* jQuery JavaScript Library v1.10.2
* http://jquery.com/
*
* Includes Sizzle.js
* http://sizzlejs.com/
*
* Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2013-07-03T13:48Z
*/
(function( window, undefined ) {
// Can't do this because several apps including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
// Support: Firefox 18+
//"use strict";
var
// The deferred used on DOM ready
readyList,
// A central reference to the root jQuery(document)
rootjQuery,
// Support: IE<10
// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
core_strundefined = typeof undefined,
// Use the correct document accordingly with window argument (sandbox)
location = window.location,
document = window.document,
docElem = document.documentElement,
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$,
// [[Class]] -> type pairs
class2type = {},
// List of deleted data cache ids, so we can reuse them
core_deletedIds = [],
core_version = "1.10.2",
// Save a reference to some core methods
core_concat = core_deletedIds.concat,
core_push = core_deletedIds.push,
core_slice = core_deletedIds.slice,
core_indexOf = core_deletedIds.indexOf,
core_toString = class2type.toString,
core_hasOwn = class2type.hasOwnProperty,
core_trim = core_version.trim,
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},
// Used for matching numbers
core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
// Used for splitting on whitespace
core_rnotwhite = /\S+/g,
// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
// A simple way to check for HTML strings
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
// Strict HTML recognition (#11290: must start with <)
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
// Match a standalone tag
rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
// JSON RegExp
rvalidchars = /^[\],:{}\s]*$/,
rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,
// Matches dashed string for camelizing
rmsPrefix = /^-ms-/,
rdashAlpha = /-([\da-z])/gi,
// Used by jQuery.camelCase as callback to replace()
fcamelCase = function( all, letter ) {
return letter.toUpperCase();
},
// The ready event handler
completed = function( event ) {
// readyState === "complete" is good enough for us to call the dom ready in oldIE
if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
detach();
jQuery.ready();
}
},
// Clean-up method for dom ready events
detach = function() {
if ( document.addEventListener ) {
document.removeEventListener( "DOMContentLoaded", completed, false );
window.removeEventListener( "load", completed, false );
} else {
document.detachEvent( "onreadystatechange", completed );
window.detachEvent( "onload", completed );
}
};
jQuery.fn = jQuery.prototype = {
// The current version of jQuery being used
jquery: core_version,
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if ( !selector ) {
return this;
}
// Handle HTML strings
if ( typeof selector === "string" ) {
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if ( match && (match[1] || !context) ) {
// HANDLE: $(html) -> $(array)
if ( match[1] ) {
context = context instanceof jQuery ? context[0] : context;
// scripts is true for back-compat
jQuery.merge( this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
// HANDLE: $(html, props)
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
// HANDLE: $(#id)
} else {
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id !== match[2] ) {
return rootjQuery.find( selector );
}
// Otherwise, we inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
}
// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
this.context = this[0] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
},
// Start with an empty selector
selector: "",
// The default length of a jQuery object is 0
length: 0,
toArray: function() {
return core_slice.call( this );
},
// Get the Nth element in the matched element set OR
// Get the whole matched element set as a clean array
get: function( num ) {
return num == null ?
// Return a 'clean' array
this.toArray() :
// Return just the object
( num < 0 ? this[ this.length + num ] : this[ num ] );
},
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;
// Return the newly-formed element set
return ret;
},
// Execute a callback for every element in the matched set.
// (You can seed the arguments with an array of args, but this is
// only used internally.)
each: function( callback, args ) {
return jQuery.each( this, callback, args );
},
ready: function( fn ) {
// Add the callback
jQuery.ready.promise().done( fn );
return this;
},
slice: function() {
return this.pushStack( core_slice.apply( this, arguments ) );
},
first: function() {
return this.eq( 0 );
},
last: function() {
return this.eq( -1 );
},
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
},
map: function( callback ) {
return this.pushStack( jQuery.map(this, function( elem, i ) {
return callback.call( elem, i, elem );
}));
},
end: function() {
return this.prevObject || this.constructor(null);
},
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
push: core_push,
sort: [].sort,
splice: [].splice
};
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
jQuery.extend({
// Unique for each copy of jQuery on the page
// Non-digits removed to match rinlinejQuery
expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
noConflict: function( deep ) {
if ( window.$ === jQuery ) {
window.$ = _$;
}
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
},
// Is the DOM ready to be used? Set to true once it occurs.
isReady: false,
// A counter to track how many items to wait for before
// the ready event fires. See #6781
readyWait: 1,
// Hold (or release) the ready event
holdReady: function( hold ) {
if ( hold ) {
jQuery.readyWait++;
} else {
jQuery.ready( true );
}
},
// Handle when the DOM is ready
ready: function( wait ) {
// Abort if there are pending holds or we're already ready
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
return;
}
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
if ( !document.body ) {
return setTimeout( jQuery.ready );
}
// Remember that the DOM is ready
jQuery.isReady = true;
// If a normal DOM Ready event fired, decrement, and wait if need be
if ( wait !== true && --jQuery.readyWait > 0 ) {
return;
}
// If there are functions bound, to execute
readyList.resolveWith( document, [ jQuery ] );
// Trigger any bound ready events
if ( jQuery.fn.trigger ) {
jQuery( document ).trigger("ready").off("ready");
}
},
// See test/unit/core.js for details concerning isFunction.
// Since version 1.3, DOM methods and functions like alert
// aren't supported. They return false on IE (#2968).
isFunction: function( obj ) {
return jQuery.type(obj) === "function";
},
isArray: Array.isArray || function( obj ) {
return jQuery.type(obj) === "array";
},
isWindow: function( obj ) {
/* jshint eqeqeq: false */
return obj != null && obj == obj.window;
},
isNumeric: function( obj ) {
return !isNaN( parseFloat(obj) ) && isFinite( obj );
},
type: function( obj ) {
if ( obj == null ) {
return String( obj );
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[ core_toString.call(obj) ] || "object" :
typeof obj;
},
isPlainObject: function( obj ) {
var key;
// Must be an Object.
// Because of IE, we also have to check the presence of the constructor property.
// Make sure that DOM nodes and window objects don't pass through, as well
if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
return false;
}
try {
// Not own constructor property must be Object
if ( obj.constructor &&
!core_hasOwn.call(obj, "constructor") &&
!core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
return false;
}
} catch ( e ) {
// IE8,9 Will throw exceptions on certain host objects #9897
return false;
}
// Support: IE<9
// Handle iteration over inherited properties before own properties.
if ( jQuery.support.ownLast ) {
for ( key in obj ) {
return core_hasOwn.call( obj, key );
}
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
for ( key in obj ) {}
return key === undefined || core_hasOwn.call( obj, key );
},
isEmptyObject: function( obj ) {
var name;
for ( name in obj ) {
return false;
}
return true;
},
error: function( msg ) {
throw new Error( msg );
},
// data: string of html
// context (optional): If specified, the fragment will be created in this context, defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
parseHTML: function( data, context, keepScripts ) {
if ( !data || typeof data !== "string" ) {
return null;
}
if ( typeof context === "boolean" ) {
keepScripts = context;
context = false;
}
context = context || document;
var parsed = rsingleTag.exec( data ),
scripts = !keepScripts && [];
// Single tag
if ( parsed ) {
return [ context.createElement( parsed[1] ) ];
}
parsed = jQuery.buildFragment( [ data ], context, scripts );
if ( scripts ) {
jQuery( scripts ).remove();
}
return jQuery.merge( [], parsed.childNodes );
},
parseJSON: function( data ) {
// Attempt to parse using the native JSON parser first
if ( window.JSON && window.JSON.parse ) {
return window.JSON.parse( data );
}
if ( data === null ) {
return data;
}
if ( typeof data === "string" ) {
// Make sure leading/trailing whitespace is removed (IE can't handle it)
data = jQuery.trim( data );
if ( data ) {
// Make sure the incoming data is actual JSON
// Logic borrowed from http://json.org/json2.js
if ( rvalidchars.test( data.replace( rvalidescape, "@" )
.replace( rvalidtokens, "]" )
.replace( rvalidbraces, "")) ) {
return ( new Function( "return " + data ) )();
}
}
}
jQuery.error( "Invalid JSON: " + data );
},
// Cross-browser xml parsing
parseXML: function( data ) {
var xml, tmp;
if ( !data || typeof data !== "string" ) {
return null;
}
try {
if ( window.DOMParser ) { // Standard
tmp = new DOMParser();
xml = tmp.parseFromString( data , "text/xml" );
} else { // IE
xml = new ActiveXObject( "Microsoft.XMLDOM" );
xml.async = "false";
xml.loadXML( data );
}
} catch( e ) {
xml = undefined;
}
if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
jQuery.error( "Invalid XML: " + data );
}
return xml;
},
noop: function() {},
// Evaluates a script in a global context
// Workarounds based on findings by Jim Driscoll
// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
globalEval: function( data ) {
if ( data && jQuery.trim( data ) ) {
// We use execScript on Internet Explorer
// We use an anonymous function so that context is window
// rather than jQuery in Firefox
( window.execScript || function( data ) {
window[ "eval" ].call( window, data );
} )( data );
}
},
// Convert dashed to camelCase; used by the css and data modules
// Microsoft forgot to hump their vendor prefix (#9572)
camelCase: function( string ) {
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},
nodeName: function( elem, name ) {
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
},
// args is for internal usage only
each: function( obj, callback, args ) {
var value,
i = 0,
length = obj.length,
isArray = isArraylike( obj );
if ( args ) {
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.apply( obj[ i ], args );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.apply( obj[ i ], args );
if ( value === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
}
return obj;
},
// Use native String.trim function wherever possible
trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
function( text ) {
return text == null ?
"" :
core_trim.call( text );
} :
// Otherwise use our own trimming functionality
function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
},
// results is for internal usage only
makeArray: function( arr, results ) {
var ret = results || [];
if ( arr != null ) {
if ( isArraylike( Object(arr) ) ) {
jQuery.merge( ret,
typeof arr === "string" ?
[ arr ] : arr
);
} else {
core_push.call( ret, arr );
}
}
return ret;
},
inArray: function( elem, arr, i ) {
var len;
if ( arr ) {
if ( core_indexOf ) {
return core_indexOf.call( arr, elem, i );
}
len = arr.length;
i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
for ( ; i < len; i++ ) {
// Skip accessing in sparse arrays
if ( i in arr && arr[ i ] === elem ) {
return i;
}
}
}
return -1;
},
merge: function( first, second ) {
var l = second.length,
i = first.length,
j = 0;
if ( typeof l === "number" ) {
for ( ; j < l; j++ ) {
first[ i++ ] = second[ j ];
}
} else {
while ( second[j] !== undefined ) {
first[ i++ ] = second[ j++ ];
}
}
first.length = i;
return first;
},
grep: function( elems, callback, inv ) {
var retVal,
ret = [],
i = 0,
length = elems.length;
inv = !!inv;
// Go through the array, only saving the items
// that pass the validator function
for ( ; i < length; i++ ) {
retVal = !!callback( elems[ i ], i );
if ( inv !== retVal ) {
ret.push( elems[ i ] );
}
}
return ret;
},
// arg is for internal usage only
map: function( elems, callback, arg ) {
var value,
i = 0,
length = elems.length,
isArray = isArraylike( elems ),
ret = [];
// Go through the array, translating each of the items to their
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret[ ret.length ] = value;
}
}
// Go through every key on the object,
} else {
for ( i in elems ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret[ ret.length ] = value;
}
}
}
// Flatten any nested arrays
return core_concat.apply( [], ret );
},
// A global GUID counter for objects
guid: 1,
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
var args, proxy, tmp;
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = core_slice.call( arguments, 2 );
proxy = function() {
return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
length = elems.length,
bulk = key == null;
// Sets many values
if ( jQuery.type( key ) === "object" ) {
chainable = true;
for ( i in key ) {
jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
}
// Sets one value
} else if ( value !== undefined ) {
chainable = true;
if ( !jQuery.isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// Bulk operations run against the entire set
if ( raw ) {
fn.call( elems, value );
fn = null;
// ...except when executing function values
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
if ( fn ) {
for ( ; i < length; i++ ) {
fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
}
}
}
return chainable ?
elems :
// Gets
bulk ?
fn.call( elems ) :
length ? fn( elems[0], key ) : emptyGet;
},
now: function() {
return ( new Date() ).getTime();
},
// A method for quickly swapping in/out CSS properties to get correct calculations.
// Note: this method belongs to the css module but it's needed here for the support module.
// If support gets modularized, this method should be moved back to the css module.
swap: function( elem, options, callback, args ) {
var ret, name,
old = {};
// Remember the old values, and insert the new ones
for ( name in options ) {
old[ name ] = elem.style[ name ];
elem.style[ name ] = options[ name ];
}
ret = callback.apply( elem, args || [] );
// Revert the old values
for ( name in options ) {
elem.style[ name ] = old[ name ];
}
return ret;
}
});
jQuery.ready.promise = function( obj ) {
if ( !readyList ) {
readyList = jQuery.Deferred();
// Catch cases where $(document).ready() is called after the browser event has already occurred.
// we once tried to use readyState "interactive" here, but it caused issues like the one
// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
if ( document.readyState === "complete" ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
setTimeout( jQuery.ready );
// Standards-based browsers support DOMContentLoaded
} else if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", completed, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", completed, false );
// If IE event model is used
} else {
// Ensure firing before onload, maybe late but safe also for iframes
document.attachEvent( "onreadystatechange", completed );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", completed );
// If IE and not a frame
// continually check to see if the document is ready
var top = false;
try {
top = window.frameElement == null && document.documentElement;
} catch(e) {}
if ( top && top.doScroll ) {
(function doScrollCheck() {
if ( !jQuery.isReady ) {
try {
// Use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
top.doScroll("left");
} catch(e) {
return setTimeout( doScrollCheck, 50 );
}
// detach all dom ready events
detach();
// and execute any waiting functions
jQuery.ready();
}
})();
}
}
}
return readyList.promise( obj );
};
// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
function isArraylike( obj ) {
var length = obj.length,
type = jQuery.type( obj );
if ( jQuery.isWindow( obj ) ) {
return false;
}
if ( obj.nodeType === 1 && length ) {
return true;
}
return type === "array" || type !== "function" &&
( length === 0 ||
typeof length === "number" && length > 0 && ( length - 1 ) in obj );
}
// All jQuery objects should point back to these
rootjQuery = jQuery(document);
/*!
* Sizzle CSS Selector Engine v1.10.2
* http://sizzlejs.com/
*
* Copyright 2013 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2013-07-03
*/
(function( window, undefined ) {
var i,
support,
cachedruns,
Expr,
getText,
isXML,
compile,
outermostContext,
sortInput,
// Local document vars
setDocument,
document,
docElem,
documentIsHTML,
rbuggyQSA,
rbuggyMatches,
matches,
contains,
// Instance-specific data
expando = "sizzle" + -(new Date()),
preferredDoc = window.document,
dirruns = 0,
done = 0,
classCache = createCache(),
tokenCache = createCache(),
compilerCache = createCache(),
hasDuplicate = false,
sortOrder = function( a, b ) {
if ( a === b ) {
hasDuplicate = true;
return 0;
}
return 0;
},
// General-purpose constants
strundefined = typeof undefined,
MAX_NEGATIVE = 1 << 31,
// Instance methods
hasOwn = ({}).hasOwnProperty,
arr = [],
pop = arr.pop,
push_native = arr.push,
push = arr.push,
slice = arr.slice,
// Use a stripped-down indexOf if we can't use a native one
indexOf = arr.indexOf || function( elem ) {
var i = 0,
len = this.length;
for ( ; i < len; i++ ) {
if ( this[i] === elem ) {
return i;
}
}
return -1;
},
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
// Regular expressions
// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
whitespace = "[\\x20\\t\\r\\n\\f]",
// http://www.w3.org/TR/css3-syntax/#characters
characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
// Loosely modeled on CSS identifier characters
// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
identifier = characterEncoding.replace( "w", "w#" ),
// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
"*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
// Prefer arguments quoted,
// then not containing pseudos/brackets,
// then attribute selectors/non-parenthetical expressions,
// then anything else
// These preferences are here to reduce the number of selectors
// needing tokenize in the PSEUDO preFilter
pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
rsibling = new RegExp( whitespace + "*[+~]" ),
rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ),
rpseudo = new RegExp( pseudos ),
ridentifier = new RegExp( "^" + identifier + "$" ),
matchExpr = {
"ID": new RegExp( "^#(" + characterEncoding + ")" ),
"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
// For use in libraries implementing .is()
// We use this for POS matching in `select`
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
rnative = /^[^{]+\{\s*\[native \w/,
// Easily-parseable/retrievable ID or TAG or CLASS selectors
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
rinputs = /^(?:input|select|textarea|button)$/i,
rheader = /^h\d$/i,
rescape = /'|\\/g,
// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
funescape = function( _, escaped, escapedWhitespace ) {
var high = "0x" + escaped - 0x10000;
// NaN means non-codepoint
// Support: Firefox
// Workaround erroneous numeric interpretation of +"0x"
return high !== high || escapedWhitespace ?
escaped :
// BMP codepoint
high < 0 ?
String.fromCharCode( high + 0x10000 ) :
// Supplemental Plane codepoint (surrogate pair)
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
};
// Optimize for push.apply( _, NodeList )
try {
push.apply(
(arr = slice.call( preferredDoc.childNodes )),
preferredDoc.childNodes
);
// Support: Android<4.0
// Detect silently failing push.apply
arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
push = { apply: arr.length ?
// Leverage slice if possible
function( target, els ) {
push_native.apply( target, slice.call(els) );
} :
// Support: IE<9
// Otherwise append directly
function( target, els ) {
var j = target.length,
i = 0;
// Can't trust NodeList.length
while ( (target[j++] = els[i++]) ) {}
target.length = j - 1;
}
};
}
function Sizzle( selector, context, results, seed ) {
var match, elem, m, nodeType,
// QSA vars
i, groups, old, nid, newContext, newSelector;
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
setDocument( context );
}
context = context || document;
results = results || [];
if ( !selector || typeof selector !== "string" ) {
return results;
}
if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
return [];
}
if ( documentIsHTML && !seed ) {
// Shortcuts
if ( (match = rquickExpr.exec( selector )) ) {
// Speed-up: Sizzle("#ID")
if ( (m = match[1]) ) {
if ( nodeType === 9 ) {
elem = context.getElementById( m );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE, Opera, and Webkit return items
// by name instead of ID
if ( elem.id === m ) {
results.push( elem );
return results;
}
} else {
return results;
}
} else {
// Context is not a document
if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
contains( context, elem ) && elem.id === m ) {
results.push( elem );
return results;
}
}
// Speed-up: Sizzle("TAG")
} else if ( match[2] ) {
push.apply( results, context.getElementsByTagName( selector ) );
return results;
// Speed-up: Sizzle(".CLASS")
} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
push.apply( results, context.getElementsByClassName( m ) );
return results;
}
}
// QSA path
if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
nid = old = expando;
newContext = context;
newSelector = nodeType === 9 && selector;
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
groups = tokenize( selector );
if ( (old = context.getAttribute("id")) ) {
nid = old.replace( rescape, "\\$&" );
} else {
context.setAttribute( "id", nid );
}
nid = "[id='" + nid + "'] ";
i = groups.length;
while ( i-- ) {
groups[i] = nid + toSelector( groups[i] );
}
newContext = rsibling.test( selector ) && context.parentNode || context;
newSelector = groups.join(",");
}
if ( newSelector ) {
try {
push.apply( results,
newContext.querySelectorAll( newSelector )
);
return results;
} catch(qsaError) {
} finally {
if ( !old ) {
context.removeAttribute("id");
}
}
}
}
}
// All others
return select( selector.replace( rtrim, "$1" ), context, results, seed );
}
/**
* Create key-value caches of limited size
* @returns {Function(string, Object)} Returns the Object data after storing it on itself with
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
* deleting the oldest entry
*/
function createCache() {
var keys = [];
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
if ( keys.push( key += " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return (cache[ key ] = value);
}
return cache;
}
/**
* Mark a function for special use by Sizzle
* @param {Function} fn The function to mark
*/
function markFunction( fn ) {
fn[ expando ] = true;
return fn;
}
/**
* Support testing using an element
* @param {Function} fn Passed the created div and expects a boolean result
*/
function assert( fn ) {
var div = document.createElement("div");
try {
return !!fn( div );
} catch (e) {
return false;
} finally {
// Remove from its parent by default
if ( div.parentNode ) {
div.parentNode.removeChild( div );
}
// release memory in IE
div = null;
}
}
/**
* Adds the same handler for all of the specified attrs
* @param {String} attrs Pipe-separated list of attributes
* @param {Function} handler The method that will be applied
*/
function addHandle( attrs, handler ) {
var arr = attrs.split("|"),
i = attrs.length;
while ( i-- ) {
Expr.attrHandle[ arr[i] ] = handler;
}
}
/**
* Checks document order of two siblings
* @param {Element} a
* @param {Element} b
* @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
*/
function siblingCheck( a, b ) {
var cur = b && a,
diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
( ~b.sourceIndex || MAX_NEGATIVE ) -
( ~a.sourceIndex || MAX_NEGATIVE );
// Use IE sourceIndex if available on both nodes
if ( diff ) {
return diff;
}
// Check if b follows a
if ( cur ) {
while ( (cur = cur.nextSibling) ) {
if ( cur === b ) {
return -1;
}
}
}
return a ? 1 : -1;
}
/**
* Returns a function to use in pseudos for input types
* @param {String} type
*/
function createInputPseudo( type ) {
return function( elem ) {
var name = elem.nodeName.toLowerCase();
return name === "input" && elem.type === type;
};
}
/**
* Returns a function to use in pseudos for buttons
* @param {String} type
*/
function createButtonPseudo( type ) {
return function( elem ) {
var name = elem.nodeName.toLowerCase();
return (name === "input" || name === "button") && elem.type === type;
};
}
/**
* Returns a function to use in pseudos for positionals
* @param {Function} fn
*/
function createPositionalPseudo( fn ) {
return markFunction(function( argument ) {
argument = +argument;
return markFunction(function( seed, matches ) {
var j,
matchIndexes = fn( [], seed.length, argument ),
i = matchIndexes.length;
// Match elements found at the specified indexes
while ( i-- ) {
if ( seed[ (j = matchIndexes[i]) ] ) {
seed[j] = !(matches[j] = seed[j]);
}
}
});
});
}
/**
* Detect xml
* @param {Element|Object} elem An element or a document
*/
isXML = Sizzle.isXML = function( elem ) {
// documentElement is verified for cases where it doesn't yet exist
// (such as loading iframes in IE - #4833)
var documentElement = elem && (elem.ownerDocument || elem).documentElement;
return documentElement ? documentElement.nodeName !== "HTML" : false;
};
// Expose support vars for convenience
support = Sizzle.support = {};
/**
* Sets document-related variables once based on the current document
* @param {Element|Object} [doc] An element or document object to use to set the document
* @returns {Object} Returns the current document
*/
setDocument = Sizzle.setDocument = function( node ) {
var doc = node ? node.ownerDocument || node : preferredDoc,
parent = doc.defaultView;
// If no document and documentElement is available, return
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
return document;
}
// Set our document
document = doc;
docElem = doc.documentElement;
// Support tests
documentIsHTML = !isXML( doc );
// Support: IE>8
// If iframe document is assigned to "document" variable and if iframe has been reloaded,
// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
// IE6-8 do not support the defaultView property so parent will be undefined
if ( parent && parent.attachEvent && parent !== parent.top ) {
parent.attachEvent( "onbeforeunload", function() {
setDocument();
});
}
/* Attributes
---------------------------------------------------------------------- */
// Support: IE<8
// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
support.attributes = assert(function( div ) {
div.className = "i";
return !div.getAttribute("className");
});
/* getElement(s)By*
---------------------------------------------------------------------- */
// Check if getElementsByTagName("*") returns only elements
support.getElementsByTagName = assert(function( div ) {
div.appendChild( doc.createComment("") );
return !div.getElementsByTagName("*").length;
});
// Check if getElementsByClassName can be trusted
support.getElementsByClassName = assert(function( div ) {
div.innerHTML = "<div class='a'></div><div class='a i'></div>";
// Support: Safari<4
// Catch class over-caching
div.firstChild.className = "i";
// Support: Opera<10
// Catch gEBCN failure to find non-leading classes
return div.getElementsByClassName("i").length === 2;
});
// Support: IE<10
// Check if getElementById returns elements by name
// The broken getElementById methods don't pick up programatically-set names,
// so use a roundabout getElementsByName test
support.getById = assert(function( div ) {
docElem.appendChild( div ).id = expando;
return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
});
// ID find and filter
if ( support.getById ) {
Expr.find["ID"] = function( id, context ) {
if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
var m = context.getElementById( id );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
return m && m.parentNode ? [m] : [];
}
};
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
return elem.getAttribute("id") === attrId;
};
};
} else {
// Support: IE6/7
// getElementById is not reliable as a find shortcut
delete Expr.find["ID"];
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
return node && node.value === attrId;
};
};
}
// Tag
Expr.find["TAG"] = support.getElementsByTagName ?
function( tag, context ) {
if ( typeof context.getElementsByTagName !== strundefined ) {
return context.getElementsByTagName( tag );
}
} :
function( tag, context ) {
var elem,
tmp = [],
i = 0,
results = context.getElementsByTagName( tag );
// Filter out possible comments
if ( tag === "*" ) {
while ( (elem = results[i++]) ) {
if ( elem.nodeType === 1 ) {
tmp.push( elem );
}
}
return tmp;
}
return results;
};
// Class
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
return context.getElementsByClassName( className );
}
};
/* QSA/matchesSelector
---------------------------------------------------------------------- */
// QSA and matchesSelector support
// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
rbuggyMatches = [];
// qSa(:focus) reports false when true (Chrome 21)
// We allow this because of a bug in IE8/9 that throws an error
// whenever `document.activeElement` is accessed on an iframe
// So, we allow :focus to pass through QSA all the time to avoid the IE error
// See http://bugs.jquery.com/ticket/13378
rbuggyQSA = [];
if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
// Build QSA regex
// Regex strategy adopted from Diego Perini
assert(function( div ) {
// Select is set to empty string on purpose
// This is to test IE's treatment of not explicitly
// setting a boolean content attribute,
// since its presence should be enough
// http://bugs.jquery.com/ticket/12359
div.innerHTML = "<select><option selected=''></option></select>";
// Support: IE8
// Boolean attributes and "value" are not treated correctly
if ( !div.querySelectorAll("[selected]").length ) {
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
}
// Webkit/Opera - :checked should return selected option elements
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
// IE8 throws error here and will not see later tests
if ( !div.querySelectorAll(":checked").length ) {
rbuggyQSA.push(":checked");
}
});
assert(function( div ) {
// Support: Opera 10-12/IE8
// ^= $= *= and empty values
// Should not select anything
// Support: Windows 8 Native Apps
// The type attribute is restricted during .innerHTML assignment
var input = doc.createElement("input");
input.setAttribute( "type", "hidden" );
div.appendChild( input ).setAttribute( "t", "" );
if ( div.querySelectorAll("[t^='']").length ) {
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
}
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
// IE8 throws error here and will not see later tests
if ( !div.querySelectorAll(":enabled").length ) {
rbuggyQSA.push( ":enabled", ":disabled" );
}
// Opera 10-11 does not throw on post-comma invalid pseudos
div.querySelectorAll("*,:x");
rbuggyQSA.push(",.*:");
});
}
if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMatchesSelector) )) ) {
assert(function( div ) {
// Check to see if it's possible to do matchesSelector
// on a disconnected node (IE 9)
support.disconnectedMatch = matches.call( div, "div" );
// This should fail with an exception
// Gecko does not error, returns false instead
matches.call( div, "[s!='']:x" );
rbuggyMatches.push( "!=", pseudos );
});
}
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
/* Contains
---------------------------------------------------------------------- */
// Element contains another
// Purposefully does not implement inclusive descendent
// As in, an element does not contain itself
contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};
/* Sorting
---------------------------------------------------------------------- */
// Document order sorting
sortOrder = docElem.compareDocumentPosition ?
function( a, b ) {
// Flag for duplicate removal
if ( a === b ) {
hasDuplicate = true;
return 0;
}
var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
if ( compare ) {
// Disconnected nodes
if ( compare & 1 ||
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
// Choose the first element that is related to our preferred document
if ( a === doc || contains(preferredDoc, a) ) {
return -1;
}
if ( b === doc || contains(preferredDoc, b) ) {
return 1;
}
// Maintain original order
return sortInput ?
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
0;
}
return compare & 4 ? -1 : 1;
}
// Not directly comparable, sort on existence of method
return a.compareDocumentPosition ? -1 : 1;
} :
function( a, b ) {
var cur,
i = 0,
aup = a.parentNode,
bup = b.parentNode,
ap = [ a ],
bp = [ b ];
// Exit early if the nodes are identical
if ( a === b ) {
hasDuplicate = true;
return 0;
// Parentless nodes are either documents or disconnected
} else if ( !aup || !bup ) {
return a === doc ? -1 :
b === doc ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
0;
// If the nodes are siblings, we can do a quick check
} else if ( aup === bup ) {
return siblingCheck( a, b );
}
// Otherwise we need full lists of their ancestors for comparison
cur = a;
while ( (cur = cur.parentNode) ) {
ap.unshift( cur );
}
cur = b;
while ( (cur = cur.parentNode) ) {
bp.unshift( cur );
}
// Walk down the tree looking for a discrepancy
while ( ap[i] === bp[i] ) {
i++;
}
return i ?
// Do a sibling check if the nodes have a common ancestor
siblingCheck( ap[i], bp[i] ) :
// Otherwise nodes in our document sort first
ap[i] === preferredDoc ? -1 :
bp[i] === preferredDoc ? 1 :
0;
};
return doc;
};
Sizzle.matches = function( expr, elements ) {
return Sizzle( expr, null, null, elements );
};
Sizzle.matchesSelector = function( elem, expr ) {
// Set document vars if needed
if ( ( elem.ownerDocument || elem ) !== document ) {
setDocument( elem );
}
// Make sure that attribute selectors are quoted
expr = expr.replace( rattributeQuotes, "='$1']" );
if ( support.matchesSelector && documentIsHTML &&
( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
try {
var ret = matches.call( elem, expr );
// IE 9's matchesSelector returns false on disconnected nodes
if ( ret || support.disconnectedMatch ||
// As well, disconnected nodes are said to be in a document
// fragment in IE 9
elem.document && elem.document.nodeType !== 11 ) {
return ret;
}
} catch(e) {}
}
return Sizzle( expr, document, null, [elem] ).length > 0;
};
Sizzle.contains = function( context, elem ) {
// Set document vars if needed
if ( ( context.ownerDocument || context ) !== document ) {
setDocument( context );
}
return contains( context, elem );
};
Sizzle.attr = function( elem, name ) {
// Set document vars if needed
if ( ( elem.ownerDocument || elem ) !== document ) {
setDocument( elem );
}
var fn = Expr.attrHandle[ name.toLowerCase() ],
// Don't get fooled by Object.prototype properties (jQuery #13807)
val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
fn( elem, name, !documentIsHTML ) :
undefined;
return val === undefined ?
support.attributes || !documentIsHTML ?
elem.getAttribute( name ) :
(val = elem.getAttributeNode(name)) && val.specified ?
val.value :
null :
val;
};
Sizzle.error = function( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
};
/**
* Document sorting and removing duplicates
* @param {ArrayLike} results
*/
Sizzle.uniqueSort = function( results ) {
var elem,
duplicates = [],
j = 0,
i = 0;
// Unless we *know* we can detect duplicates, assume their presence
hasDuplicate = !support.detectDuplicates;
sortInput = !support.sortStable && results.slice( 0 );
results.sort( sortOrder );
if ( hasDuplicate ) {
while ( (elem = results[i++]) ) {
if ( elem === results[ i ] ) {
j = duplicates.push( i );
}
}
while ( j-- ) {
results.splice( duplicates[ j ], 1 );
}
}
return results;
};
/**
* Utility function for retrieving the text value of an array of DOM nodes
* @param {Array|Element} elem
*/
getText = Sizzle.getText = function( elem ) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
if ( !nodeType ) {
// If no nodeType, this is expected to be an array
for ( ; (node = elem[i]); i++ ) {
// Do not traverse comment nodes
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent for elements
// innerText usage removed for consistency of new lines (see #11153)
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
// Do not include comment or processing instruction nodes
return ret;
};
Expr = Sizzle.selectors = {
// Can be adjusted by the user
cacheLength: 50,
createPseudo: markFunction,
match: matchExpr,
attrHandle: {},
find: {},
relative: {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
},
preFilter: {
"ATTR": function( match ) {
match[1] = match[1].replace( runescape, funescape );
// Move the given value to match[3] whether quoted or unquoted
match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
if ( match[2] === "~=" ) {
match[3] = " " + match[3] + " ";
}
return match.slice( 0, 4 );
},
"CHILD": function( match ) {
/* matches from matchExpr["CHILD"]
1 type (only|nth|...)
2 what (child|of-type)
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
4 xn-component of xn+y argument ([+-]?\d*n|)
5 sign of xn-component
6 x of xn-component
7 sign of y-component
8 y of y-component
*/
match[1] = match[1].toLowerCase();
if ( match[1].slice( 0, 3 ) === "nth" ) {
// nth-* requires argument
if ( !match[3] ) {
Sizzle.error( match[0] );
}
// numeric x and y parameters for Expr.filter.CHILD
// remember that false/true cast respectively to 0/1
match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
// other types prohibit arguments
} else if ( match[3] ) {
Sizzle.error( match[0] );
}
return match;
},
"PSEUDO": function( match ) {
var excess,
unquoted = !match[5] && match[2];
if ( matchExpr["CHILD"].test( match[0] ) ) {
return null;
}
// Accept quoted arguments as-is
if ( match[3] && match[4] !== undefined ) {
match[2] = match[4];
// Strip excess characters from unquoted arguments
} else if ( unquoted && rpseudo.test( unquoted ) &&
// Get excess from tokenize (recursively)
(excess = tokenize( unquoted, true )) &&
// advance to the next closing parenthesis
(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
// excess is a negative index
match[0] = match[0].slice( 0, excess );
match[2] = unquoted.slice( 0, excess );
}
// Return only captures needed by the pseudo filter method (type and argument)
return match.slice( 0, 3 );
}
},
filter: {
"TAG": function( nodeNameSelector ) {
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
return nodeNameSelector === "*" ?
function() { return true; } :
function( elem ) {
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
};
},
"CLASS": function( className ) {
var pattern = classCache[ className + " " ];
return pattern ||
(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
classCache( className, function( elem ) {
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
});
},
"ATTR": function( name, operator, check ) {
return function( elem ) {
var result = Sizzle.attr( elem, name );
if ( result == null ) {
return operator === "!=";
}
if ( !operator ) {
return true;
}
result += "";
return operator === "=" ? result === check :
operator === "!=" ? result !== check :
operator === "^=" ? check && result.indexOf( check ) === 0 :
operator === "*=" ? check && result.indexOf( check ) > -1 :
operator === "$=" ? check && result.slice( -check.length ) === check :
operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
false;
};
},
"CHILD": function( type, what, argument, first, last ) {
var simple = type.slice( 0, 3 ) !== "nth",
forward = type.slice( -4 ) !== "last",
ofType = what === "of-type";
return first === 1 && last === 0 ?
// Shortcut for :nth-*(n)
function( elem ) {
return !!elem.parentNode;
} :
function( elem, context, xml ) {
var cache, outerCache, node, diff, nodeIndex, start,
dir = simple !== forward ? "nextSibling" : "previousSibling",
parent = elem.parentNode,
name = ofType && elem.nodeName.toLowerCase(),
useCache = !xml && !ofType;
if ( parent ) {
// :(first|last|only)-(child|of-type)
if ( simple ) {
while ( dir ) {
node = elem;
while ( (node = node[ dir ]) ) {
if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
return false;
}
}
// Reverse direction for :only-* (if we haven't yet done so)
start = dir = type === "only" && !start && "nextSibling";
}
return true;
}
start = [ forward ? parent.firstChild : parent.lastChild ];
// non-xml :nth-child(...) stores cache data on `parent`
if ( forward && useCache ) {
// Seek `elem` from a previously-cached index
outerCache = parent[ expando ] || (parent[ expando ] = {});
cache = outerCache[ type ] || [];
nodeIndex = cache[0] === dirruns && cache[1];
diff = cache[0] === dirruns && cache[2];
node = nodeIndex && parent.childNodes[ nodeIndex ];
while ( (node = ++nodeIndex && node && node[ dir ] ||
// Fallback to seeking `elem` from the start
(diff = nodeIndex = 0) || start.pop()) ) {
// When found, cache indexes on `parent` and break
if ( node.nodeType === 1 && ++diff && node === elem ) {
outerCache[ type ] = [ dirruns, nodeIndex, diff ];
break;
}
}
// Use previously-cached element index if available
} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
diff = cache[1];
// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
} else {
// Use the same loop as above to seek `elem` from the start
while ( (node = ++nodeIndex && node && node[ dir ] ||
(diff = nodeIndex = 0) || start.pop()) ) {
if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
// Cache the index of each encountered element
if ( useCache ) {
(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
}
if ( node === elem ) {
break;
}
}
}
}
// Incorporate the offset, then check against cycle size
diff -= last;
return diff === first || ( diff % first === 0 && diff / first >= 0 );
}
};
},
"PSEUDO": function( pseudo, argument ) {
// pseudo-class names are case-insensitive
// http://www.w3.org/TR/selectors/#pseudo-classes
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
// Remember that setFilters inherits from pseudos
var args,
fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
Sizzle.error( "unsupported pseudo: " + pseudo );
// The user may use createPseudo to indicate that
// arguments are needed to create the filter function
// just as Sizzle does
if ( fn[ expando ] ) {
return fn( argument );
}
// But maintain support for old signatures
if ( fn.length > 1 ) {
args = [ pseudo, pseudo, "", argument ];
return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
markFunction(function( seed, matches ) {
var idx,
matched = fn( seed, argument ),
i = matched.length;
while ( i-- ) {
idx = indexOf.call( seed, matched[i] );
seed[ idx ] = !( matches[ idx ] = matched[i] );
}
}) :
function( elem ) {
return fn( elem, 0, args );
};
}
return fn;
}
},
pseudos: {
// Potentially complex pseudos
"not": markFunction(function( selector ) {
// Trim the selector passed to compile
// to avoid treating leading and trailing
// spaces as combinators
var input = [],
results = [],
matcher = compile( selector.replace( rtrim, "$1" ) );
return matcher[ expando ] ?
markFunction(function( seed, matches, context, xml ) {
var elem,
unmatched = matcher( seed, null, xml, [] ),
i = seed.length;
// Match elements unmatched by `matcher`
while ( i-- ) {
if ( (elem = unmatched[i]) ) {
seed[i] = !(matches[i] = elem);
}
}
}) :
function( elem, context, xml ) {
input[0] = elem;
matcher( input, null, xml, results );
return !results.pop();
};
}),
"has": markFunction(function( selector ) {
return function( elem ) {
return Sizzle( selector, elem ).length > 0;
};
}),
"contains": markFunction(function( text ) {
return function( elem ) {
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
};
}),
// "Whether an element is represented by a :lang() selector
// is based solely on the element's language value
// being equal to the identifier C,
// or beginning with the identifier C immediately followed by "-".
// The matching of C against the element's language value is performed case-insensitively.
// The identifier C does not have to be a valid language name."
// http://www.w3.org/TR/selectors/#lang-pseudo
"lang": markFunction( function( lang ) {
// lang value must be a valid identifier
if ( !ridentifier.test(lang || "") ) {
Sizzle.error( "unsupported lang: " + lang );
}
lang = lang.replace( runescape, funescape ).toLowerCase();
return function( elem ) {
var elemLang;
do {
if ( (elemLang = documentIsHTML ?
elem.lang :
elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
elemLang = elemLang.toLowerCase();
return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
}
} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
return false;
};
}),
// Miscellaneous
"target": function( elem ) {
var hash = window.location && window.location.hash;
return hash && hash.slice( 1 ) === elem.id;
},
"root": function( elem ) {
return elem === docElem;
},
"focus": function( elem ) {
return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
},
// Boolean properties
"enabled": function( elem ) {
return elem.disabled === false;
},
"disabled": function( elem ) {
return elem.disabled === true;
},
"checked": function( elem ) {
// In CSS3, :checked should return both checked and selected elements
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
var nodeName = elem.nodeName.toLowerCase();
return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
},
"selected": function( elem ) {
// Accessing this property makes selected-by-default
// options in Safari work properly
if ( elem.parentNode ) {
elem.parentNode.selectedIndex;
}
return elem.selected === true;
},
// Contents
"empty": function( elem ) {
// http://www.w3.org/TR/selectors/#empty-pseudo
// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
// not comment, processing instructions, or others
// Thanks to Diego Perini for the nodeName shortcut
// Greater than "@" means alpha characters (specifically not starting with "#" or "?")
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
return false;
}
}
return true;
},
"parent": function( elem ) {
return !Expr.pseudos["empty"]( elem );
},
// Element/input types
"header": function( elem ) {
return rheader.test( elem.nodeName );
},
"input": function( elem ) {
return rinputs.test( elem.nodeName );
},
"button": function( elem ) {
var name = elem.nodeName.toLowerCase();
return name === "input" && elem.type === "button" || name === "button";
},
"text": function( elem ) {
var attr;
// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
// use getAttribute instead to test this case
return elem.nodeName.toLowerCase() === "input" &&
elem.type === "text" &&
( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
},
// Position-in-collection
"first": createPositionalPseudo(function() {
return [ 0 ];
}),
"last": createPositionalPseudo(function( matchIndexes, length ) {
return [ length - 1 ];
}),
"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
return [ argument < 0 ? argument + length : argument ];
}),
"even": createPositionalPseudo(function( matchIndexes, length ) {
var i = 0;
for ( ; i < length; i += 2 ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
"odd": createPositionalPseudo(function( matchIndexes, length ) {
var i = 1;
for ( ; i < length; i += 2 ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
var i = argument < 0 ? argument + length : argument;
for ( ; --i >= 0; ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
var i = argument < 0 ? argument + length : argument;
for ( ; ++i < length; ) {
matchIndexes.push( i );
}
return matchIndexes;
})
}
};
Expr.pseudos["nth"] = Expr.pseudos["eq"];
// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
Expr.pseudos[ i ] = createButtonPseudo( i );
}
// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();
function tokenize( selector, parseOnly ) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[ selector + " " ];
if ( cached ) {
return parseOnly ? 0 : cached.slice( 0 );
}
soFar = selector;
groups = [];
preFilters = Expr.preFilter;
while ( soFar ) {
// Comma and first run
if ( !matched || (match = rcomma.exec( soFar )) ) {
if ( match ) {
// Don't consume trailing commas as valid
soFar = soFar.slice( match[0].length ) || soFar;
}
groups.push( tokens = [] );
}
matched = false;
// Combinators
if ( (match = rcombinators.exec( soFar )) ) {
matched = match.shift();
tokens.push({
value: matched,
// Cast descendant combinators to space
type: match[0].replace( rtrim, " " )
});
soFar = soFar.slice( matched.length );
}
// Filters
for ( type in Expr.filter ) {
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
(match = preFilters[ type ]( match ))) ) {
matched = match.shift();
tokens.push({
value: matched,
type: type,
matches: match
});
soFar = soFar.slice( matched.length );
}
}
if ( !matched ) {
break;
}
}
// Return the length of the invalid excess
// if we're just parsing
// Otherwise, throw an error or return tokens
return parseOnly ?
soFar.length :
soFar ?
Sizzle.error( selector ) :
// Cache the tokens
tokenCache( selector, groups ).slice( 0 );
}
function toSelector( tokens ) {
var i = 0,
len = tokens.length,
selector = "";
for ( ; i < len; i++ ) {
selector += tokens[i].value;
}
return selector;
}
function addCombinator( matcher, combinator, base ) {
var dir = combinator.dir,
checkNonElements = base && dir === "parentNode",
doneName = done++;
return combinator.first ?
// Check against closest ancestor/preceding element
function( elem, context, xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
return matcher( elem, context, xml );
}
}
} :
// Check against all ancestor/preceding elements
function( elem, context, xml ) {
var data, cache, outerCache,
dirkey = dirruns + " " + doneName;
// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
if ( xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
if ( matcher( elem, context, xml ) ) {
return true;
}
}
}
} else {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
outerCache = elem[ expando ] || (elem[ expando ] = {});
if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
if ( (data = cache[1]) === true || data === cachedruns ) {
return data === true;
}
} else {
cache = outerCache[ dir ] = [ dirkey ];
cache[1] = matcher( elem, context, xml ) || cachedruns;
if ( cache[1] === true ) {
return true;
}
}
}
}
}
};
}
function elementMatcher( matchers ) {
return matchers.length > 1 ?
function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
} :
matchers[0];
}
function condense( unmatched, map, filter, context, xml ) {
var elem,
newUnmatched = [],
i = 0,
len = unmatched.length,
mapped = map != null;
for ( ; i < len; i++ ) {
if ( (elem = unmatched[i]) ) {
if ( !filter || filter( elem, context, xml ) ) {
newUnmatched.push( elem );
if ( mapped ) {
map.push( i );
}
}
}
}
return newUnmatched;
}
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
if ( postFilter && !postFilter[ expando ] ) {
postFilter = setMatcher( postFilter );
}
if ( postFinder && !postFinder[ expando ] ) {
postFinder = setMatcher( postFinder, postSelector );
}
return markFunction(function( seed, results, context, xml ) {
var temp, i, elem,
preMap = [],
postMap = [],
preexisting = results.length,
// Get initial elements from seed or context
elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
// Prefilter to get matcher input, preserving a map for seed-results synchronization
matcherIn = preFilter && ( seed || !selector ) ?
condense( elems, preMap, preFilter, context, xml ) :
elems,
matcherOut = matcher ?
// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
// ...intermediate processing is necessary
[] :
// ...otherwise use results directly
results :
matcherIn;
// Find primary matches
if ( matcher ) {
matcher( matcherIn, matcherOut, context, xml );
}
// Apply postFilter
if ( postFilter ) {
temp = condense( matcherOut, postMap );
postFilter( temp, [], context, xml );
// Un-match failing elements by moving them back to matcherIn
i = temp.length;
while ( i-- ) {
if ( (elem = temp[i]) ) {
matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
}
}
}
if ( seed ) {
if ( postFinder || preFilter ) {
if ( postFinder ) {
// Get the final matcherOut by condensing this intermediate into postFinder contexts
temp = [];
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) ) {
// Restore matcherIn since elem is not yet a final match
temp.push( (matcherIn[i] = elem) );
}
}
postFinder( null, (matcherOut = []), temp, xml );
}
// Move matched elements from seed to results to keep them synchronized
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) &&
(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
seed[temp] = !(results[temp] = elem);
}
}
}
// Add elements to results, through postFinder if defined
} else {
matcherOut = condense(
matcherOut === results ?
matcherOut.splice( preexisting, matcherOut.length ) :
matcherOut
);
if ( postFinder ) {
postFinder( null, results, matcherOut, xml );
} else {
push.apply( results, matcherOut );
}
}
});
}
function matcherFromTokens( tokens ) {
var checkContext, matcher, j,
len = tokens.length,
leadingRelative = Expr.relative[ tokens[0].type ],
implicitRelative = leadingRelative || Expr.relative[" "],
i = leadingRelative ? 1 : 0,
// The foundational matcher ensures that elements are reachable from top-level context(s)
matchContext = addCombinator( function( elem ) {
return elem === checkContext;
}, implicitRelative, true ),
matchAnyContext = addCombinator( function( elem ) {
return indexOf.call( checkContext, elem ) > -1;
}, implicitRelative, true ),
matchers = [ function( elem, context, xml ) {
return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
(checkContext = context).nodeType ?
matchContext( elem, context, xml ) :
matchAnyContext( elem, context, xml ) );
} ];
for ( ; i < len; i++ ) {
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
} else {
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
// Return special upon seeing a positional matcher
if ( matcher[ expando ] ) {
// Find the next relative operator (if any) for proper handling
j = ++i;
for ( ; j < len; j++ ) {
if ( Expr.relative[ tokens[j].type ] ) {
break;
}
}
return setMatcher(
i > 1 && elementMatcher( matchers ),
i > 1 && toSelector(
// If the preceding token was a descendant combinator, insert an implicit any-element `*`
tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
).replace( rtrim, "$1" ),
matcher,
i < j && matcherFromTokens( tokens.slice( i, j ) ),
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
j < len && toSelector( tokens )
);
}
matchers.push( matcher );
}
}
return elementMatcher( matchers );
}
function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
// A counter to specify which element is currently being matched
var matcherCachedRuns = 0,
bySet = setMatchers.length > 0,
byElement = elementMatchers.length > 0,
superMatcher = function( seed, context, xml, results, expandContext ) {
var elem, j, matcher,
setMatched = [],
matchedCount = 0,
i = "0",
unmatched = seed && [],
outermost = expandContext != null,
contextBackup = outermostContext,
// We must always have either seed elements or context
elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
// Use integer dirruns iff this is the outermost matcher
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);
if ( outermost ) {
outermostContext = context !== document && context;
cachedruns = matcherCachedRuns;
}
// Add elements passing elementMatchers directly to results
// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
for ( ; (elem = elems[i]) != null; i++ ) {
if ( byElement && elem ) {
j = 0;
while ( (matcher = elementMatchers[j++]) ) {
if ( matcher( elem, context, xml ) ) {
results.push( elem );
break;
}
}
if ( outermost ) {
dirruns = dirrunsUnique;
cachedruns = ++matcherCachedRuns;
}
}
// Track unmatched elements for set filters
if ( bySet ) {
// They will have gone through all possible matchers
if ( (elem = !matcher && elem) ) {
matchedCount--;
}
// Lengthen the array for every element, matched or not
if ( seed ) {
unmatched.push( elem );
}
}
}
// Apply set filters to unmatched elements
matchedCount += i;
if ( bySet && i !== matchedCount ) {
j = 0;
while ( (matcher = setMatchers[j++]) ) {
matcher( unmatched, setMatched, context, xml );
}
if ( seed ) {
// Reintegrate element matches to eliminate the need for sorting
if ( matchedCount > 0 ) {
while ( i-- ) {
if ( !(unmatched[i] || setMatched[i]) ) {
setMatched[i] = pop.call( results );
}
}
}
// Discard index placeholder values to get only actual matches
setMatched = condense( setMatched );
}
// Add matches to results
push.apply( results, setMatched );
// Seedless set matches succeeding multiple successful matchers stipulate sorting
if ( outermost && !seed && setMatched.length > 0 &&
( matchedCount + setMatchers.length ) > 1 ) {
Sizzle.uniqueSort( results );
}
}
// Override manipulation of globals by nested matchers
if ( outermost ) {
dirruns = dirrunsUnique;
outermostContext = contextBackup;
}
return unmatched;
};
return bySet ?
markFunction( superMatcher ) :
superMatcher;
}
compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
var i,
setMatchers = [],
elementMatchers = [],
cached = compilerCache[ selector + " " ];
if ( !cached ) {
// Generate a function of recursive functions that can be used to check each element
if ( !group ) {
group = tokenize( selector );
}
i = group.length;
while ( i-- ) {
cached = matcherFromTokens( group[i] );
if ( cached[ expando ] ) {
setMatchers.push( cached );
} else {
elementMatchers.push( cached );
}
}
// Cache the compiled function
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
}
return cached;
};
function multipleContexts( selector, contexts, results ) {
var i = 0,
len = contexts.length;
for ( ; i < len; i++ ) {
Sizzle( selector, contexts[i], results );
}
return results;
}
function select( selector, context, results, seed ) {
var i, tokens, token, type, find,
match = tokenize( selector );
if ( !seed ) {
// Try to minimize operations if there is only one group
if ( match.length === 1 ) {
// Take a shortcut and set the context if the root selector is an ID
tokens = match[0] = match[0].slice( 0 );
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
support.getById && context.nodeType === 9 && documentIsHTML &&
Expr.relative[ tokens[1].type ] ) {
context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
if ( !context ) {
return results;
}
selector = selector.slice( tokens.shift().value.length );
}
// Fetch a seed set for right-to-left matching
i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
while ( i-- ) {
token = tokens[i];
// Abort if we hit a combinator
if ( Expr.relative[ (type = token.type) ] ) {
break;
}
if ( (find = Expr.find[ type ]) ) {
// Search, expanding context for leading sibling combinators
if ( (seed = find(
token.matches[0].replace( runescape, funescape ),
rsibling.test( tokens[0].type ) && context.parentNode || context
)) ) {
// If seed is empty or no tokens remain, we can return early
tokens.splice( i, 1 );
selector = seed.length && toSelector( tokens );
if ( !selector ) {
push.apply( results, seed );
return results;
}
break;
}
}
}
}
}
// Compile and execute a filtering function
// Provide `match` to avoid retokenization if we modified the selector above
compile( selector, match )(
seed,
context,
!documentIsHTML,
results,
rsibling.test( selector )
);
return results;
}
// One-time assignments
// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
// Support: Chrome<14
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = hasDuplicate;
// Initialize against the default document
setDocument();
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert(function( div1 ) {
// Should return 1, but returns 4 (following)
return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});
// Support: IE<8
// Prevent attribute/property "interpolation"
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( div ) {
div.innerHTML = "<a href='#'></a>";
return div.firstChild.getAttribute("href") === "#" ;
}) ) {
addHandle( "type|href|height|width", function( elem, name, isXML ) {
if ( !isXML ) {
return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
}
});
}
// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( div ) {
div.innerHTML = "<input/>";
div.firstChild.setAttribute( "value", "" );
return div.firstChild.getAttribute( "value" ) === "";
}) ) {
addHandle( "value", function( elem, name, isXML ) {
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
return elem.defaultValue;
}
});
}
// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( div ) {
return div.getAttribute("disabled") == null;
}) ) {
addHandle( booleans, function( elem, name, isXML ) {
var val;
if ( !isXML ) {
return (val = elem.getAttributeNode( name )) && val.specified ?
val.value :
elem[ name ] === true ? name.toLowerCase() : null;
}
});
}
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
})( window );
// String to Object options format cache
var optionsCache = {};
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
});
return object;
}
/*
* Create a callback list using the following parameters:
*
* options: an optional list of space-separated options that will change how
* the callback list behaves or a more traditional option object
*
* By default a callback list will act like an event callback list and can be
* "fired" multiple times.
*
* Possible options:
*
* once: will ensure the callback list can only be fired once (like a Deferred)
*
* memory: will keep track of previous values and will call any callback added
* after the list has been fired right away with the latest "memorized"
* values (like a Deferred)
*
* unique: will ensure a callback can only be added once (no duplicate in the list)
*
* stopOnFalse: interrupt callings when a callback returns false
*
*/
jQuery.Callbacks = function( options ) {
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
var // Flag to know if list is currently firing
firing,
// Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired
fired,
// End of the loop when firing
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
// First callback to fire (used internally by add and fireWith)
firingStart,
// Actual callback list
list = [],
// Stack of fire calls for repeatable lists
stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
memory = options.memory && data;
fired = true;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
},
// Actual Callbacks object
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
// First, we save the current length
var start = list.length;
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) {
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
},
// Remove a callback from the list
remove: function() {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
has: function( fn ) {
return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
},
// Remove all callbacks from the list
empty: function() {
list = [];
firingLength = 0;
return this;
},
// Have the list do nothing anymore
disable: function() {
list = stack = memory = undefined;
return this;
},
// Is it disabled?
disabled: function() {
return !list;
},
// Lock the list in its current state
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
// Is it locked?
locked: function() {
return !stack;
},
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
if ( list && ( !fired || stack ) ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
if ( firing ) {
stack.push( args );
} else {
fire( args );
}
}
return this;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
}
};
return self;
};
jQuery.extend({
Deferred: function( func ) {
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",
promise = {
state: function() {
return state;
},
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// Keep pipe for back-compat
promise.pipe = promise.then;
// Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add;
// Handle state
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
// Make the deferred a promise
promise.promise( deferred );
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
},
// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length,
// the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
// add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}
// if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}
return deferred.promise();
}
});
jQuery.support = (function( support ) {
var all, a, input, select, fragment, opt, eventName, isSupported, i,
div = document.createElement("div");
// Setup
div.setAttribute( "className", "t" );
div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
// Finish early in limited (non-browser) environments
all = div.getElementsByTagName("*") || [];
a = div.getElementsByTagName("a")[ 0 ];
if ( !a || !a.style || !all.length ) {
return support;
}
// First batch of tests
select = document.createElement("select");
opt = select.appendChild( document.createElement("option") );
input = div.getElementsByTagName("input")[ 0 ];
a.style.cssText = "top:1px;float:left;opacity:.5";
// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
support.getSetAttribute = div.className !== "t";
// IE strips leading whitespace when .innerHTML is used
support.leadingWhitespace = div.firstChild.nodeType === 3;
// Make sure that tbody elements aren't automatically inserted
// IE will insert them into empty tables
support.tbody = !div.getElementsByTagName("tbody").length;
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
support.htmlSerialize = !!div.getElementsByTagName("link").length;
// Get the style information from getAttribute
// (IE uses .cssText instead)
support.style = /top/.test( a.getAttribute("style") );
// Make sure that URLs aren't manipulated
// (IE normalizes it by default)
support.hrefNormalized = a.getAttribute("href") === "/a";
// Make sure that element opacity exists
// (IE uses filter instead)
// Use a regex to work around a WebKit issue. See #5145
support.opacity = /^0.5/.test( a.style.opacity );
// Verify style float existence
// (IE uses styleFloat instead of cssFloat)
support.cssFloat = !!a.style.cssFloat;
// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
support.checkOn = !!input.value;
// Make sure that a selected-by-default option has a working selected property.
// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
support.optSelected = opt.selected;
// Tests for enctype support on a form (#6743)
support.enctype = !!document.createElement("form").enctype;
// Makes sure cloning an html5 element does not cause problems
// Where outerHTML is undefined, this still works
support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>";
// Will be defined later
support.inlineBlockNeedsLayout = false;
support.shrinkWrapBlocks = false;
support.pixelPosition = false;
support.deleteExpando = true;
support.noCloneEvent = true;
support.reliableMarginRight = true;
support.boxSizingReliable = true;
// Make sure checked status is properly cloned
input.checked = true;
support.noCloneChecked = input.cloneNode( true ).checked;
// Make sure that the options inside disabled selects aren't marked as disabled
// (WebKit marks them as disabled)
select.disabled = true;
support.optDisabled = !opt.disabled;
// Support: IE<9
try {
delete div.test;
} catch( e ) {
support.deleteExpando = false;
}
// Check if we can trust getAttribute("value")
input = document.createElement("input");
input.setAttribute( "value", "" );
support.input = input.getAttribute( "value" ) === "";
// Check if an input maintains its value after becoming a radio
input.value = "t";
input.setAttribute( "type", "radio" );
support.radioValue = input.value === "t";
// #11217 - WebKit loses check when the name is after the checked attribute
input.setAttribute( "checked", "t" );
input.setAttribute( "name", "t" );
fragment = document.createDocumentFragment();
fragment.appendChild( input );
// Check if a disconnected checkbox will retain its checked
// value of true after appended to the DOM (IE6/7)
support.appendChecked = input.checked;
// WebKit doesn't clone checked state correctly in fragments
support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
// Support: IE<9
// Opera does not clone events (and typeof div.attachEvent === undefined).
// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
if ( div.attachEvent ) {
div.attachEvent( "onclick", function() {
support.noCloneEvent = false;
});
div.cloneNode( true ).click();
}
// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
for ( i in { submit: true, change: true, focusin: true }) {
div.setAttribute( eventName = "on" + i, "t" );
support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
}
div.style.backgroundClip = "content-box";
div.cloneNode( true ).style.backgroundClip = "";
support.clearCloneStyle = div.style.backgroundClip === "content-box";
// Support: IE<9
// Iteration over object's inherited properties before its own.
for ( i in jQuery( support ) ) {
break;
}
support.ownLast = i !== "0";
// Run tests that need a body at doc ready
jQuery(function() {
var container, marginDiv, tds,
divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",
body = document.getElementsByTagName("body")[0];
if ( !body ) {
// Return for frameset docs that don't have a body
return;
}
container = document.createElement("div");
container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
body.appendChild( container ).appendChild( div );
// Support: IE8
// Check if table cells still have offsetWidth/Height when they are set
// to display:none and there are still other visible table cells in a
// table row; if so, offsetWidth/Height are not reliable for use when
// determining if an element has been hidden directly using
// display:none (it is still safe to use offsets if a parent element is
// hidden; don safety goggles and see bug #4512 for more information).
div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
tds = div.getElementsByTagName("td");
tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
isSupported = ( tds[ 0 ].offsetHeight === 0 );
tds[ 0 ].style.display = "";
tds[ 1 ].style.display = "none";
// Support: IE8
// Check if empty table cells still have offsetWidth/Height
support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
// Check box-sizing and margin behavior.
div.innerHTML = "";
div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
// Workaround failing boxSizing test due to offsetWidth returning wrong value
// with some non-1 values of body zoom, ticket #13543
jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
support.boxSizing = div.offsetWidth === 4;
});
// Use window.getComputedStyle because jsdom on node.js will break without it.
if ( window.getComputedStyle ) {
support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
// Check if div with explicit width and no margin-right incorrectly
// gets computed margin-right based on width of container. (#3333)
// Fails in WebKit before Feb 2011 nightlies
// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
marginDiv = div.appendChild( document.createElement("div") );
marginDiv.style.cssText = div.style.cssText = divReset;
marginDiv.style.marginRight = marginDiv.style.width = "0";
div.style.width = "1px";
support.reliableMarginRight =
!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
}
if ( typeof div.style.zoom !== core_strundefined ) {
// Support: IE<8
// Check if natively block-level elements act like inline-block
// elements when setting their display to 'inline' and giving
// them layout
div.innerHTML = "";
div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
// Support: IE6
// Check if elements with layout shrink-wrap their children
div.style.display = "block";
div.innerHTML = "<div></div>";
div.firstChild.style.width = "5px";
support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
if ( support.inlineBlockNeedsLayout ) {
// Prevent IE 6 from affecting layout for positioned elements #11048
// Prevent IE from shrinking the body in IE 7 mode #12869
// Support: IE<8
body.style.zoom = 1;
}
}
body.removeChild( container );
// Null elements to avoid leaks in IE
container = div = tds = marginDiv = null;
});
// Null elements to avoid leaks in IE
all = select = fragment = opt = a = input = null;
return support;
})({});
var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
rmultiDash = /([A-Z])/g;
function internalData( elem, name, data, pvt /* Internal Use Only */ ){
if ( !jQuery.acceptData( elem ) ) {
return;
}
var ret, thisCache,
internalKey = jQuery.expando,
// We have to handle DOM nodes and JS objects differently because IE6-7
// can't GC object references properly across the DOM-JS boundary
isNode = elem.nodeType,
// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically
cache = isNode ? jQuery.cache : elem,
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
// Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all
if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
return;
}
if ( !id ) {
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;
} else {
id = internalKey;
}
}
if ( !cache[ id ] ) {
// Avoid exposing jQuery metadata on plain JS objects when the object
// is serialized using JSON.stringify
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}
thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object's internal data
// cache in order to avoid key collisions between internal data and user-defined
// data.
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {};
}
thisCache = thisCache.data;
}
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}
// Check for both converted-to-camel and non-converted data property names
// If a data property was specified
if ( typeof name === "string" ) {
// First Try to find as-is property data
ret = thisCache[ name ];
// Test for null|undefined property data
if ( ret == null ) {
// Try to find the camelCased property
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
ret = thisCache;
}
return ret;
}
function internalRemoveData( elem, name, pvt ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var thisCache, i,
isNode = elem.nodeType,
// See jQuery.data for more information
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
// If there is already no cache entry for this object, there is no
// purpose in continuing
if ( !cache[ id ] ) {
return;
}
if ( name ) {
thisCache = pvt ? cache[ id ] : cache[ id ].data;
if ( thisCache ) {
// Support array or space separated string names for data keys
if ( !jQuery.isArray( name ) ) {
// try the string as a key before any manipulation
if ( name in thisCache ) {
name = [ name ];
} else {
// split the camel cased version by spaces unless a key with the spaces exists
name = jQuery.camelCase( name );
if ( name in thisCache ) {
name = [ name ];
} else {
name = name.split(" ");
}
}
} else {
// If "name" is an array of keys...
// When data is initially created, via ("key", "val") signature,
// keys will be converted to camelCase.
// Since there is no way to tell _how_ a key was added, remove
// both plain key and camelCase key. #12786
// This will only penalize the array argument path.
name = name.concat( jQuery.map( name, jQuery.camelCase ) );
}
i = name.length;
while ( i-- ) {
delete thisCache[ name[i] ];
}
// If there is no data left in the cache, we want to continue
// and let the cache object itself get destroyed
if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
return;
}
}
}
// See jQuery.data for more information
if ( !pvt ) {
delete cache[ id ].data;
// Don't destroy the parent cache unless the internal data object
// had been the only thing left in it
if ( !isEmptyDataObject( cache[ id ] ) ) {
return;
}
}
// Destroy the cache
if ( isNode ) {
jQuery.cleanData( [ elem ], true );
// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
/* jshint eqeqeq: false */
} else if ( jQuery.support.deleteExpando || cache != cache.window ) {
/* jshint eqeqeq: true */
delete cache[ id ];
// When all else fails, null
} else {
cache[ id ] = null;
}
}
jQuery.extend({
cache: {},
// The following elements throw uncatchable exceptions if you
// attempt to add expando properties to them.
noData: {
"applet": true,
"embed": true,
// Ban all objects except for Flash (which handle expandos)
"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
},
hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
return !!elem && !isEmptyDataObject( elem );
},
data: function( elem, name, data ) {
return internalData( elem, name, data );
},
removeData: function( elem, name ) {
return internalRemoveData( elem, name );
},
// For internal use only.
_data: function( elem, name, data ) {
return internalData( elem, name, data, true );
},
_removeData: function( elem, name ) {
return internalRemoveData( elem, name, true );
},
// A method for determining if a DOM node can handle the data expando
acceptData: function( elem ) {
// Do not set data on non-element because it will not be cleared (#8335).
if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
return false;
}
var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
// nodes accept data unless otherwise specified; rejection can be conditional
return !noData || noData !== true && elem.getAttribute("classid") === noData;
}
});
jQuery.fn.extend({
data: function( key, value ) {
var attrs, name,
data = null,
i = 0,
elem = this[0];
// Special expections of .data basically thwart jQuery.access,
// so implement the relevant behavior ourselves
// Gets all values
if ( key === undefined ) {
if ( this.length ) {
data = jQuery.data( elem );
if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
attrs = elem.attributes;
for ( ; i < attrs.length; i++ ) {
name = attrs[i].name;
if ( name.indexOf("data-") === 0 ) {
name = jQuery.camelCase( name.slice(5) );
dataAttr( elem, name, data[ name ] );
}
}
jQuery._data( elem, "parsedAttrs", true );
}
}
return data;
}
// Sets multiple values
if ( typeof key === "object" ) {
return this.each(function() {
jQuery.data( this, key );
});
}
return arguments.length > 1 ?
// Sets one value
this.each(function() {
jQuery.data( this, key, value );
}) :
// Gets one value
// Try to fetch any internally stored data first
elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
},
removeData: function( key ) {
return this.each(function() {
jQuery.removeData( this, key );
});
}
});
function dataAttr( elem, key, data ) {
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
if ( data === undefined && elem.nodeType === 1 ) {
var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
// Only convert to a number if it doesn't change the string
+data + "" === data ? +data :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch( e ) {}
// Make sure we set the data so it isn't changed later
jQuery.data( elem, key, data );
} else {
data = undefined;
}
}
return data;
}
// checks a cache object for emptiness
function isEmptyDataObject( obj ) {
var name;
for ( name in obj ) {
// if the public data object is empty, the private is still empty
if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
continue;
}
if ( name !== "toJSON" ) {
return false;
}
}
return true;
}
jQuery.extend({
queue: function( elem, type, data ) {
var queue;
if ( elem ) {
type = ( type || "fx" ) + "queue";
queue = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
if ( !queue || jQuery.isArray(data) ) {
queue = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
queue.push( data );
}
}
return queue || [];
}
},
dequeue: function( elem, type ) {
type = type || "fx";
var queue = jQuery.queue( elem, type ),
startLength = queue.length,
fn = queue.shift(),
hooks = jQuery._queueHooks( elem, type ),
next = function() {
jQuery.dequeue( elem, type );
};
// If the fx queue is dequeued, always remove the progress sentinel
if ( fn === "inprogress" ) {
fn = queue.shift();
startLength--;
}
if ( fn ) {
// Add a progress sentinel to prevent the fx queue from being
// automatically dequeued
if ( type === "fx" ) {
queue.unshift( "inprogress" );
}
// clear up the last queue stop function
delete hooks.stop;
fn.call( elem, next, hooks );
}
if ( !startLength && hooks ) {
hooks.empty.fire();
}
},
// not intended for public consumption - generates a queueHooks object, or returns the current one
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
return jQuery._data( elem, key ) || jQuery._data( elem, key, {
empty: jQuery.Callbacks("once memory").add(function() {
jQuery._removeData( elem, type + "queue" );
jQuery._removeData( elem, key );
})
});
}
});
jQuery.fn.extend({
queue: function( type, data ) {
var setter = 2;
if ( typeof type !== "string" ) {
data = type;
type = "fx";
setter--;
}
if ( arguments.length < setter ) {
return jQuery.queue( this[0], type );
}
return data === undefined ?
this :
this.each(function() {
var queue = jQuery.queue( this, type, data );
// ensure a hooks for this queue
jQuery._queueHooks( this, type );
if ( type === "fx" && queue[0] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
});
},
dequeue: function( type ) {
return this.each(function() {
jQuery.dequeue( this, type );
});
},
// Based off of the plugin by Clint Helfers, with permission.
// http://blindsignals.com/index.php/2009/07/jquery-delay/
delay: function( time, type ) {
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
type = type || "fx";
return this.queue( type, function( next, hooks ) {
var timeout = setTimeout( next, time );
hooks.stop = function() {
clearTimeout( timeout );
};
});
},
clearQueue: function( type ) {
return this.queue( type || "fx", [] );
},
// Get a promise resolved when queues of a certain type
// are emptied (fx is the type by default)
promise: function( type, obj ) {
var tmp,
count = 1,
defer = jQuery.Deferred(),
elements = this,
i = this.length,
resolve = function() {
if ( !( --count ) ) {
defer.resolveWith( elements, [ elements ] );
}
};
if ( typeof type !== "string" ) {
obj = type;
type = undefined;
}
type = type || "fx";
while( i-- ) {
tmp = jQuery._data( elements[ i ], type + "queueHooks" );
if ( tmp && tmp.empty ) {
count++;
tmp.empty.add( resolve );
}
}
resolve();
return defer.promise( obj );
}
});
var nodeHook, boolHook,
rclass = /[\t\r\n\f]/g,
rreturn = /\r/g,
rfocusable = /^(?:input|select|textarea|button|object)$/i,
rclickable = /^(?:a|area)$/i,
ruseDefault = /^(?:checked|selected)$/i,
getSetAttribute = jQuery.support.getSetAttribute,
getSetInput = jQuery.support.input;
jQuery.fn.extend({
attr: function( name, value ) {
return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
},
removeAttr: function( name ) {
return this.each(function() {
jQuery.removeAttr( this, name );
});
},
prop: function( name, value ) {
return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
},
removeProp: function( name ) {
name = jQuery.propFix[ name ] || name;
return this.each(function() {
// try/catch handles cases where IE balks (such as removing a property on window)
try {
this[ name ] = undefined;
delete this[ name ];
} catch( e ) {}
});
},
addClass: function( value ) {
var classes, elem, cur, clazz, j,
i = 0,
len = this.length,
proceed = typeof value === "string" && value;
if ( jQuery.isFunction( value ) ) {
return this.each(function( j ) {
jQuery( this ).addClass( value.call( this, j, this.className ) );
});
}
if ( proceed ) {
// The disjunction here is for better compressibility (see removeClass)
classes = ( value || "" ).match( core_rnotwhite ) || [];
for ( ; i < len; i++ ) {
elem = this[ i ];
cur = elem.nodeType === 1 && ( elem.className ?
( " " + elem.className + " " ).replace( rclass, " " ) :
" "
);
if ( cur ) {
j = 0;
while ( (clazz = classes[j++]) ) {
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
cur += clazz + " ";
}
}
elem.className = jQuery.trim( cur );
}
}
}
return this;
},
removeClass: function( value ) {
var classes, elem, cur, clazz, j,
i = 0,
len = this.length,
proceed = arguments.length === 0 || typeof value === "string" && value;
if ( jQuery.isFunction( value ) ) {
return this.each(function( j ) {
jQuery( this ).removeClass( value.call( this, j, this.className ) );
});
}
if ( proceed ) {
classes = ( value || "" ).match( core_rnotwhite ) || [];
for ( ; i < len; i++ ) {
elem = this[ i ];
// This expression is here for better compressibility (see addClass)
cur = elem.nodeType === 1 && ( elem.className ?
( " " + elem.className + " " ).replace( rclass, " " ) :
""
);
if ( cur ) {
j = 0;
while ( (clazz = classes[j++]) ) {
// Remove *all* instances
while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
cur = cur.replace( " " + clazz + " ", " " );
}
}
elem.className = value ? jQuery.trim( cur ) : "";
}
}
}
return this;
},
toggleClass: function( value, stateVal ) {
var type = typeof value;
if ( typeof stateVal === "boolean" && type === "string" ) {
return stateVal ? this.addClass( value ) : this.removeClass( value );
}
if ( jQuery.isFunction( value ) ) {
return this.each(function( i ) {
jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
});
}
return this.each(function() {
if ( type === "string" ) {
// toggle individual class names
var className,
i = 0,
self = jQuery( this ),
classNames = value.match( core_rnotwhite ) || [];
while ( (className = classNames[ i++ ]) ) {
// check each className given, space separated list
if ( self.hasClass( className ) ) {
self.removeClass( className );
} else {
self.addClass( className );
}
}
// Toggle whole class name
} else if ( type === core_strundefined || type === "boolean" ) {
if ( this.className ) {
// store className if set
jQuery._data( this, "__className__", this.className );
}
// If the element has a class name or if we're passed "false",
// then remove the whole classname (if there was one, the above saved it).
// Otherwise bring back whatever was previously saved (if anything),
// falling back to the empty string if nothing was stored.
this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
}
});
},
hasClass: function( selector ) {
var className = " " + selector + " ",
i = 0,
l = this.length;
for ( ; i < l; i++ ) {
if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
return true;
}
}
return false;
},
val: function( value ) {
var ret, hooks, isFunction,
elem = this[0];
if ( !arguments.length ) {
if ( elem ) {
hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
return ret;
}
ret = elem.value;
return typeof ret === "string" ?
// handle most common string cases
ret.replace(rreturn, "") :
// handle cases where value is null/undef or number
ret == null ? "" : ret;
}
return;
}
isFunction = jQuery.isFunction( value );
return this.each(function( i ) {
var val;
if ( this.nodeType !== 1 ) {
return;
}
if ( isFunction ) {
val = value.call( this, i, jQuery( this ).val() );
} else {
val = value;
}
// Treat null/undefined as ""; convert numbers to string
if ( val == null ) {
val = "";
} else if ( typeof val === "number" ) {
val += "";
} else if ( jQuery.isArray( val ) ) {
val = jQuery.map(val, function ( value ) {
return value == null ? "" : value + "";
});
}
hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
// If set returns undefined, fall back to normal setting
if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
this.value = val;
}
});
}
});
jQuery.extend({
valHooks: {
option: {
get: function( elem ) {
// Use proper attribute retrieval(#6932, #12072)
var val = jQuery.find.attr( elem, "value" );
return val != null ?
val :
elem.text;
}
},
select: {
get: function( elem ) {
var value, option,
options = elem.options,
index = elem.selectedIndex,
one = elem.type === "select-one" || index < 0,
values = one ? null : [],
max = one ? index + 1 : options.length,
i = index < 0 ?
max :
one ? index : 0;
// Loop through all the selected options
for ( ; i < max; i++ ) {
option = options[ i ];
// oldIE doesn't update selected after form reset (#2551)
if ( ( option.selected || i === index ) &&
// Don't return options that are disabled or in a disabled optgroup
( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
// Get the specific value for the option
value = jQuery( option ).val();
// We don't need an array for one selects
if ( one ) {
return value;
}
// Multi-Selects return an array
values.push( value );
}
}
return values;
},
set: function( elem, value ) {
var optionSet, option,
options = elem.options,
values = jQuery.makeArray( value ),
i = options.length;
while ( i-- ) {
option = options[ i ];
if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
optionSet = true;
}
}
// force browsers to behave consistently when non-matching value is set
if ( !optionSet ) {
elem.selectedIndex = -1;
}
return values;
}
}
},
attr: function( elem, name, value ) {
var hooks, ret,
nType = elem.nodeType;
// don't get/set attributes on text, comment and attribute nodes
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return;
}
// Fallback to prop when attributes are not supported
if ( typeof elem.getAttribute === core_strundefined ) {
return jQuery.prop( elem, name, value );
}
// All attributes are lowercase
// Grab necessary hook if one is defined
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
name = name.toLowerCase();
hooks = jQuery.attrHooks[ name ] ||
( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
}
if ( value !== undefined ) {
if ( value === null ) {
jQuery.removeAttr( elem, name );
} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
return ret;
} else {
elem.setAttribute( name, value + "" );
return value;
}
} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
return ret;
} else {
ret = jQuery.find.attr( elem, name );
// Non-existent attributes return null, we normalize to undefined
return ret == null ?
undefined :
ret;
}
},
removeAttr: function( elem, value ) {
var name, propName,
i = 0,
attrNames = value && value.match( core_rnotwhite );
if ( attrNames && elem.nodeType === 1 ) {
while ( (name = attrNames[i++]) ) {
propName = jQuery.propFix[ name ] || name;
// Boolean attributes get special treatment (#10870)
if ( jQuery.expr.match.bool.test( name ) ) {
// Set corresponding property to false
if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
elem[ propName ] = false;
// Support: IE<9
// Also clear defaultChecked/defaultSelected (if appropriate)
} else {
elem[ jQuery.camelCase( "default-" + name ) ] =
elem[ propName ] = false;
}
// See #9699 for explanation of this approach (setting first, then removal)
} else {
jQuery.attr( elem, name, "" );
}
elem.removeAttribute( getSetAttribute ? name : propName );
}
}
},
attrHooks: {
type: {
set: function( elem, value ) {
if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
// Setting the type on a radio button after the value resets the value in IE6-9
// Reset value to default in case type is set after value during creation
var val = elem.value;
elem.setAttribute( "type", value );
if ( val ) {
elem.value = val;
}
return value;
}
}
}
},
propFix: {
"for": "htmlFor",
"class": "className"
},
prop: function( elem, name, value ) {
var ret, hooks, notxml,
nType = elem.nodeType;
// don't get/set properties on text, comment and attribute nodes
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return;
}
notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
if ( notxml ) {
// Fix name and attach hooks
name = jQuery.propFix[ name ] || name;
hooks = jQuery.propHooks[ name ];
}
if ( value !== undefined ) {
return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
ret :
( elem[ name ] = value );
} else {
return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
ret :
elem[ name ];
}
},
propHooks: {
tabIndex: {
get: function( elem ) {
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
// Use proper attribute retrieval(#12072)
var tabindex = jQuery.find.attr( elem, "tabindex" );
return tabindex ?
parseInt( tabindex, 10 ) :
rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
0 :
-1;
}
}
}
});
// Hooks for boolean attributes
boolHook = {
set: function( elem, value, name ) {
if ( value === false ) {
// Remove boolean attributes when set to false
jQuery.removeAttr( elem, name );
} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
// IE<8 needs the *property* name
elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
// Use defaultChecked and defaultSelected for oldIE
} else {
elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
}
return name;
}
};
jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?
function( elem, name, isXML ) {
var fn = jQuery.expr.attrHandle[ name ],
ret = isXML ?
undefined :
/* jshint eqeqeq: false */
(jQuery.expr.attrHandle[ name ] = undefined) !=
getter( elem, name, isXML ) ?
name.toLowerCase() :
null;
jQuery.expr.attrHandle[ name ] = fn;
return ret;
} :
function( elem, name, isXML ) {
return isXML ?
undefined :
elem[ jQuery.camelCase( "default-" + name ) ] ?
name.toLowerCase() :
null;
};
});
// fix oldIE attroperties
if ( !getSetInput || !getSetAttribute ) {
jQuery.attrHooks.value = {
set: function( elem, value, name ) {
if ( jQuery.nodeName( elem, "input" ) ) {
// Does not return so that setAttribute is also used
elem.defaultValue = value;
} else {
// Use nodeHook if defined (#1954); otherwise setAttribute is fine
return nodeHook && nodeHook.set( elem, value, name );
}
}
};
}
// IE6/7 do not support getting/setting some attributes with get/setAttribute
if ( !getSetAttribute ) {
// Use this for any attribute in IE6/7
// This fixes almost every IE6/7 issue
nodeHook = {
set: function( elem, value, name ) {
// Set the existing or create a new attribute node
var ret = elem.getAttributeNode( name );
if ( !ret ) {
elem.setAttributeNode(
(ret = elem.ownerDocument.createAttribute( name ))
);
}
ret.value = value += "";
// Break association with cloned elements by also using setAttribute (#9646)
return name === "value" || value === elem.getAttribute( name ) ?
value :
undefined;
}
};
jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords =
// Some attributes are constructed with empty-string values when not defined
function( elem, name, isXML ) {
var ret;
return isXML ?
undefined :
(ret = elem.getAttributeNode( name )) && ret.value !== "" ?
ret.value :
null;
};
jQuery.valHooks.button = {
get: function( elem, name ) {
var ret = elem.getAttributeNode( name );
return ret && ret.specified ?
ret.value :
undefined;
},
set: nodeHook.set
};
// Set contenteditable to false on removals(#10429)
// Setting to empty string throws an error as an invalid value
jQuery.attrHooks.contenteditable = {
set: function( elem, value, name ) {
nodeHook.set( elem, value === "" ? false : value, name );
}
};
// Set width and height to auto instead of 0 on empty string( Bug #8150 )
// This is for removals
jQuery.each([ "width", "height" ], function( i, name ) {
jQuery.attrHooks[ name ] = {
set: function( elem, value ) {
if ( value === "" ) {
elem.setAttribute( name, "auto" );
return value;
}
}
};
});
}
// Some attributes require a special call on IE
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !jQuery.support.hrefNormalized ) {
// href/src property should get the full normalized URL (#10299/#12915)
jQuery.each([ "href", "src" ], function( i, name ) {
jQuery.propHooks[ name ] = {
get: function( elem ) {
return elem.getAttribute( name, 4 );
}
};
});
}
if ( !jQuery.support.style ) {
jQuery.attrHooks.style = {
get: function( elem ) {
// Return undefined in the case of empty string
// Note: IE uppercases css property names, but if we were to .toLowerCase()
// .cssText, that would destroy case senstitivity in URL's, like in "background"
return elem.style.cssText || undefined;
},
set: function( elem, value ) {
return ( elem.style.cssText = value + "" );
}
};
}
// Safari mis-reports the default selected property of an option
// Accessing the parent's selectedIndex property fixes it
if ( !jQuery.support.optSelected ) {
jQuery.propHooks.selected = {
get: function( elem ) {
var parent = elem.parentNode;
if ( parent ) {
parent.selectedIndex;
// Make sure that it also works with optgroups, see #5701
if ( parent.parentNode ) {
parent.parentNode.selectedIndex;
}
}
return null;
}
};
}
jQuery.each([
"tabIndex",
"readOnly",
"maxLength",
"cellSpacing",
"cellPadding",
"rowSpan",
"colSpan",
"useMap",
"frameBorder",
"contentEditable"
], function() {
jQuery.propFix[ this.toLowerCase() ] = this;
});
// IE6/7 call enctype encoding
if ( !jQuery.support.enctype ) {
jQuery.propFix.enctype = "encoding";
}
// Radios and checkboxes getter/setter
jQuery.each([ "radio", "checkbox" ], function() {
jQuery.valHooks[ this ] = {
set: function( elem, value ) {
if ( jQuery.isArray( value ) ) {
return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
}
}
};
if ( !jQuery.support.checkOn ) {
jQuery.valHooks[ this ].get = function( elem ) {
// Support: Webkit
// "" is returned instead of "on" if a value isn't specified
return elem.getAttribute("value") === null ? "on" : elem.value;
};
}
});
var rformElems = /^(?:input|select|textarea)$/i,
rkeyEvent = /^key/,
rmouseEvent = /^(?:mouse|contextmenu)|click/,
rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
function returnTrue() {
return true;
}
function returnFalse() {
return false;
}
function safeActiveElement() {
try {
return document.activeElement;
} catch ( err ) { }
}
/*
* Helper functions for managing events -- not part of the public interface.
* Props to Dean Edwards' addEvent library for many of the ideas.
*/
jQuery.event = {
global: {},
add: function( elem, types, handler, data, selector ) {
var tmp, events, t, handleObjIn,
special, eventHandle, handleObj,
handlers, type, namespaces, origType,
elemData = jQuery._data( elem );
// Don't attach events to noData or text/comment nodes (but allow plain objects)
if ( !elemData ) {
return;
}
// Caller can pass in an object of custom data in lieu of the handler
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// Make sure that the handler has a unique ID, used to find/remove it later
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// Init the element's event structure and main handler, if this is the first
if ( !(events = elemData.events) ) {
events = elemData.events = {};
}
if ( !(eventHandle = elemData.handle) ) {
eventHandle = elemData.handle = function( e ) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;
}
// Handle multiple events separated by a space
types = ( types || "" ).match( core_rnotwhite ) || [""];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[t] ) || [];
type = origType = tmp[1];
namespaces = ( tmp[2] || "" ).split( "." ).sort();
// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
}
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[ type ] || {};
// If selector defined, determine special event api type, otherwise given type
type = ( selector ? special.delegateType : special.bindType ) || type;
// Update special based on newly reset type
special = jQuery.event.special[ type ] || {};
// handleObj is passed to all event handlers
handleObj = jQuery.extend({
type: type,
origType: origType,
data: data,
handler: handler,
guid: handler.guid,
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
namespace: namespaces.join(".")
}, handleObjIn );
// Init the event handler queue if we're the first
if ( !(handlers = events[ type ]) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
// Only use addEventListener/attachEvent if the special events handler returns false
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
}
if ( special.add ) {
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// Add to the element's handler list, delegates in front
if ( selector ) {
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
}
// Keep track of which events have ever been used, for event optimization
jQuery.event.global[ type ] = true;
}
// Nullify elem to prevent memory leaks in IE
elem = null;
},
// Detach an event or set of events from an element
remove: function( elem, types, handler, selector, mappedTypes ) {
var j, handleObj, tmp,
origCount, t, events,
special, handlers, type,
namespaces, origType,
elemData = jQuery.hasData( elem ) && jQuery._data( elem );
if ( !elemData || !(events = elemData.events) ) {
return;
}
// Once for each type.namespace in types; type may be omitted
types = ( types || "" ).match( core_rnotwhite ) || [""];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[t] ) || [];
type = origType = tmp[1];
namespaces = ( tmp[2] || "" ).split( "." ).sort();
// Unbind all events (on this namespace, if provided) for the element
if ( !type ) {
for ( type in events ) {
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
}
continue;
}
special = jQuery.event.special[ type ] || {};
type = ( selector ? special.delegateType : special.bindType ) || type;
handlers = events[ type ] || [];
tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
// Remove matching events
origCount = j = handlers.length;
while ( j-- ) {
handleObj = handlers[ j ];
if ( ( mappedTypes || origType === handleObj.origType ) &&
( !handler || handler.guid === handleObj.guid ) &&
( !tmp || tmp.test( handleObj.namespace ) ) &&
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
handlers.splice( j, 1 );
if ( handleObj.selector ) {
handlers.delegateCount--;
}
if ( special.remove ) {
special.remove.call( elem, handleObj );
}
}
}
// Remove generic event handler if we removed something and no more handlers exist
// (avoids potential for endless recursion during removal of special event handlers)
if ( origCount && !handlers.length ) {
if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
jQuery.removeEvent( elem, type, elemData.handle );
}
delete events[ type ];
}
}
// Remove the expando if it's no longer used
if ( jQuery.isEmptyObject( events ) ) {
delete elemData.handle;
// removeData also checks for emptiness and clears the expando if empty
// so use it instead of delete
jQuery._removeData( elem, "events" );
}
},
trigger: function( event, data, elem, onlyHandlers ) {
var handle, ontype, cur,
bubbleType, special, tmp, i,
eventPath = [ elem || document ],
type = core_hasOwn.call( event, "type" ) ? event.type : event,
namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
cur = tmp = elem = elem || document;
// Don't do events on text and comment nodes
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
// focus/blur morphs to focusin/out; ensure we're not firing them right now
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
return;
}
if ( type.indexOf(".") >= 0 ) {
// Namespaced trigger; create a regexp to match event type in handle()
namespaces = type.split(".");
type = namespaces.shift();
namespaces.sort();
}
ontype = type.indexOf(":") < 0 && "on" + type;
// Caller can pass in a jQuery.Event object, Object, or just an event type string
event = event[ jQuery.expando ] ?
event :
new jQuery.Event( type, typeof event === "object" && event );
// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
event.isTrigger = onlyHandlers ? 2 : 3;
event.namespace = namespaces.join(".");
event.namespace_re = event.namespace ?
new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
null;
// Clean up the event in case it is being reused
event.result = undefined;
if ( !event.target ) {
event.target = elem;
}
// Clone any incoming data and prepend the event, creating the handler arg list
data = data == null ?
[ event ] :
jQuery.makeArray( data, [ event ] );
// Allow special events to draw outside the lines
special = jQuery.event.special[ type ] || {};
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
return;
}
// Determine event propagation path in advance, per W3C events spec (#9951)
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
bubbleType = special.delegateType || type;
if ( !rfocusMorph.test( bubbleType + type ) ) {
cur = cur.parentNode;
}
for ( ; cur; cur = cur.parentNode ) {
eventPath.push( cur );
tmp = cur;
}
// Only add window if we got to document (e.g., not plain obj or detached DOM)
if ( tmp === (elem.ownerDocument || document) ) {
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
}
// Fire handlers on the event path
i = 0;
while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
event.type = i > 1 ?
bubbleType :
special.bindType || type;
// jQuery handler
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
}
// Native handler
handle = ontype && cur[ ontype ];
if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
event.preventDefault();
}
}
event.type = type;
// If nobody prevented the default action, do it now
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
jQuery.acceptData( elem ) ) {
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
// Don't re-trigger an onFOO event when we call its FOO() method
tmp = elem[ ontype ];
if ( tmp ) {
elem[ ontype ] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
jQuery.event.triggered = type;
try {
elem[ type ]();
} catch ( e ) {
// IE<9 dies on focus/blur to hidden element (#1486,#12518)
// only reproducible on winXP IE8 native, not IE9 in IE8 mode
}
jQuery.event.triggered = undefined;
if ( tmp ) {
elem[ ontype ] = tmp;
}
}
}
}
return event.result;
},
dispatch: function( event ) {
// Make a writable jQuery.Event from the native event object
event = jQuery.event.fix( event );
var i, ret, handleObj, matched, j,
handlerQueue = [],
args = core_slice.call( arguments ),
handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
special = jQuery.event.special[ event.type ] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
args[0] = event;
event.delegateTarget = this;
// Call the preDispatch hook for the mapped type, and let it bail if desired
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
return;
}
// Determine handlers
handlerQueue = jQuery.event.handlers.call( this, event, handlers );
// Run delegates first; they may want to stop propagation beneath us
i = 0;
while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
event.currentTarget = matched.elem;
j = 0;
while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
// Triggered event must either 1) have no namespace, or
// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
event.handleObj = handleObj;
event.data = handleObj.data;
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
.apply( matched.elem, args );
if ( ret !== undefined ) {
if ( (event.result = ret) === false ) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
// Call the postDispatch hook for the mapped type
if ( special.postDispatch ) {
special.postDispatch.call( this, event );
}
return event.result;
},
handlers: function( event, handlers ) {
var sel, handleObj, matches, i,
handlerQueue = [],
delegateCount = handlers.delegateCount,
cur = event.target;
// Find delegate handlers
// Black-hole SVG <use> instance trees (#13180)
// Avoid non-left-click bubbling in Firefox (#3861)
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
/* jshint eqeqeq: false */
for ( ; cur != this; cur = cur.parentNode || this ) {
/* jshint eqeqeq: true */
// Don't check non-elements (#13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
matches = [];
for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ];
// Don't conflict with Object.prototype properties (#13203)
sel = handleObj.selector + " ";
if ( matches[ sel ] === undefined ) {
matches[ sel ] = handleObj.needsContext ?
jQuery( sel, this ).index( cur ) >= 0 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
if ( matches[ sel ] ) {
matches.push( handleObj );
}
}
if ( matches.length ) {
handlerQueue.push({ elem: cur, handlers: matches });
}
}
}
}
// Add the remaining (directly-bound) handlers
if ( delegateCount < handlers.length ) {
handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
}
return handlerQueue;
},
fix: function( event ) {
if ( event[ jQuery.expando ] ) {
return event;
}
// Create a writable copy of the event object and normalize some properties
var i, prop, copy,
type = event.type,
originalEvent = event,
fixHook = this.fixHooks[ type ];
if ( !fixHook ) {
this.fixHooks[ type ] = fixHook =
rmouseEvent.test( type ) ? this.mouseHooks :
rkeyEvent.test( type ) ? this.keyHooks :
{};
}
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
event = new jQuery.Event( originalEvent );
i = copy.length;
while ( i-- ) {
prop = copy[ i ];
event[ prop ] = originalEvent[ prop ];
}
// Support: IE<9
// Fix target property (#1925)
if ( !event.target ) {
event.target = originalEvent.srcElement || document;
}
// Support: Chrome 23+, Safari?
// Target should not be a text node (#504, #13143)
if ( event.target.nodeType === 3 ) {
event.target = event.target.parentNode;
}
// Support: IE<9
// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
event.metaKey = !!event.metaKey;
return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
},
// Includes some event props shared by KeyEvent and MouseEvent
props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
fixHooks: {},
keyHooks: {
props: "char charCode key keyCode".split(" "),
filter: function( event, original ) {
// Add which for key events
if ( event.which == null ) {
event.which = original.charCode != null ? original.charCode : original.keyCode;
}
return event;
}
},
mouseHooks: {
props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
filter: function( event, original ) {
var body, eventDoc, doc,
button = original.button,
fromElement = original.fromElement;
// Calculate pageX/Y if missing and clientX/Y available
if ( event.pageX == null && original.clientX != null ) {
eventDoc = event.target.ownerDocument || document;
doc = eventDoc.documentElement;
body = eventDoc.body;
event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
}
// Add relatedTarget, if necessary
if ( !event.relatedTarget && fromElement ) {
event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
}
// Add which for click: 1 === left; 2 === middle; 3 === right
// Note: button is not normalized, so don't use it
if ( !event.which && button !== undefined ) {
event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
}
return event;
}
},
special: {
load: {
// Prevent triggered image.load events from bubbling to window.load
noBubble: true
},
focus: {
// Fire native event if possible so blur/focus sequence is correct
trigger: function() {
if ( this !== safeActiveElement() && this.focus ) {
try {
this.focus();
return false;
} catch ( e ) {
// Support: IE<9
// If we error on focus to hidden element (#1486, #12518),
// let .trigger() run the handlers
}
}
},
delegateType: "focusin"
},
blur: {
trigger: function() {
if ( this === safeActiveElement() && this.blur ) {
this.blur();
return false;
}
},
delegateType: "focusout"
},
click: {
// For checkbox, fire native event so checked state will be right
trigger: function() {
if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
this.click();
return false;
}
},
// For cross-browser consistency, don't fire native .click() on links
_default: function( event ) {
return jQuery.nodeName( event.target, "a" );
}
},
beforeunload: {
postDispatch: function( event ) {
// Even when returnValue equals to undefined Firefox will still show alert
if ( event.result !== undefined ) {
event.originalEvent.returnValue = event.result;
}
}
}
},
simulate: function( type, elem, event, bubble ) {
// Piggyback on a donor event to simulate a different one.
// Fake originalEvent to avoid donor's stopPropagation, but if the
// simulated event prevents default then we do the same on the donor.
var e = jQuery.extend(
new jQuery.Event(),
event,
{
type: type,
isSimulated: true,
originalEvent: {}
}
);
if ( bubble ) {
jQuery.event.trigger( e, null, elem );
} else {
jQuery.event.dispatch.call( elem, e );
}
if ( e.isDefaultPrevented() ) {
event.preventDefault();
}
}
};
jQuery.removeEvent = document.removeEventListener ?
function( elem, type, handle ) {
if ( elem.removeEventListener ) {
elem.removeEventListener( type, handle, false );
}
} :
function( elem, type, handle ) {
var name = "on" + type;
if ( elem.detachEvent ) {
// #8545, #7054, preventing memory leaks for custom events in IE6-8
// detachEvent needed property on element, by name of that event, to properly expose it to GC
if ( typeof elem[ name ] === core_strundefined ) {
elem[ name ] = null;
}
elem.detachEvent( name, handle );
}
};
jQuery.Event = function( src, props ) {
// Allow instantiation without the 'new' keyword
if ( !(this instanceof jQuery.Event) ) {
return new jQuery.Event( src, props );
}
// Event object
if ( src && src.type ) {
this.originalEvent = src;
this.type = src.type;
// Events bubbling up the document may have been marked as prevented
// by a handler lower down the tree; reflect the correct value.
this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
// Event type
} else {
this.type = src;
}
// Put explicitly provided properties onto the event object
if ( props ) {
jQuery.extend( this, props );
}
// Create a timestamp if incoming event doesn't have one
this.timeStamp = src && src.timeStamp || jQuery.now();
// Mark it as fixed
this[ jQuery.expando ] = true;
};
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse,
preventDefault: function() {
var e = this.originalEvent;
this.isDefaultPrevented = returnTrue;
if ( !e ) {
return;
}
// If preventDefault exists, run it on the original event
if ( e.preventDefault ) {
e.preventDefault();
// Support: IE
// Otherwise set the returnValue property of the original event to false
} else {
e.returnValue = false;
}
},
stopPropagation: function() {
var e = this.originalEvent;
this.isPropagationStopped = returnTrue;
if ( !e ) {
return;
}
// If stopPropagation exists, run it on the original event
if ( e.stopPropagation ) {
e.stopPropagation();
}
// Support: IE
// Set the cancelBubble property of the original event to true
e.cancelBubble = true;
},
stopImmediatePropagation: function() {
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
}
};
// Create mouseenter/leave events using mouseover/out and event-time checks
jQuery.each({
mouseenter: "mouseover",
mouseleave: "mouseout"
}, function( orig, fix ) {
jQuery.event.special[ orig ] = {
delegateType: fix,
bindType: fix,
handle: function( event ) {
var ret,
target = this,
related = event.relatedTarget,
handleObj = event.handleObj;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = fix;
}
return ret;
}
};
});
// IE submit delegation
if ( !jQuery.support.submitBubbles ) {
jQuery.event.special.submit = {
setup: function() {
// Only need this for delegated form submit events
if ( jQuery.nodeName( this, "form" ) ) {
return false;
}
// Lazy-add a submit handler when a descendant form may potentially be submitted
jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
// Node name check avoids a VML-related crash in IE (#9807)
var elem = e.target,
form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
if ( form && !jQuery._data( form, "submitBubbles" ) ) {
jQuery.event.add( form, "submit._submit", function( event ) {
event._submit_bubble = true;
});
jQuery._data( form, "submitBubbles", true );
}
});
// return undefined since we don't need an event listener
},
postDispatch: function( event ) {
// If form was submitted by the user, bubble the event up the tree
if ( event._submit_bubble ) {
delete event._submit_bubble;
if ( this.parentNode && !event.isTrigger ) {
jQuery.event.simulate( "submit", this.parentNode, event, true );
}
}
},
teardown: function() {
// Only need this for delegated form submit events
if ( jQuery.nodeName( this, "form" ) ) {
return false;
}
// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
jQuery.event.remove( this, "._submit" );
}
};
}
// IE change delegation and checkbox/radio fix
if ( !jQuery.support.changeBubbles ) {
jQuery.event.special.change = {
setup: function() {
if ( rformElems.test( this.nodeName ) ) {
// IE doesn't fire change on a check/radio until blur; trigger it on click
// after a propertychange. Eat the blur-change in special.change.handle.
// This still fires onchange a second time for check/radio after blur.
if ( this.type === "checkbox" || this.type === "radio" ) {
jQuery.event.add( this, "propertychange._change", function( event ) {
if ( event.originalEvent.propertyName === "checked" ) {
this._just_changed = true;
}
});
jQuery.event.add( this, "click._change", function( event ) {
if ( this._just_changed && !event.isTrigger ) {
this._just_changed = false;
}
// Allow triggered, simulated change events (#11500)
jQuery.event.simulate( "change", this, event, true );
});
}
return false;
}
// Delegated event; lazy-add a change handler on descendant inputs
jQuery.event.add( this, "beforeactivate._change", function( e ) {
var elem = e.target;
if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
jQuery.event.add( elem, "change._change", function( event ) {
if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
jQuery.event.simulate( "change", this.parentNode, event, true );
}
});
jQuery._data( elem, "changeBubbles", true );
}
});
},
handle: function( event ) {
var elem = event.target;
// Swallow native change events from checkbox/radio, we already triggered them above
if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
return event.handleObj.handler.apply( this, arguments );
}
},
teardown: function() {
jQuery.event.remove( this, "._change" );
return !rformElems.test( this.nodeName );
}
};
}
// Create "bubbling" focus and blur events
if ( !jQuery.support.focusinBubbles ) {
jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
// Attach a single capturing handler while someone wants focusin/focusout
var attaches = 0,
handler = function( event ) {
jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
};
jQuery.event.special[ fix ] = {
setup: function() {
if ( attaches++ === 0 ) {
document.addEventListener( orig, handler, true );
}
},
teardown: function() {
if ( --attaches === 0 ) {
document.removeEventListener( orig, handler, true );
}
}
};
});
}
jQuery.fn.extend({
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var type, origFn;
// Types can be a map of types/handlers
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
if ( typeof selector !== "string" ) {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
for ( type in types ) {
this.on( type, selector, data, types[ type ], one );
}
return this;
}
if ( data == null && fn == null ) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if ( fn == null ) {
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
if ( fn === false ) {
fn = returnFalse;
} else if ( !fn ) {
return this;
}
if ( one === 1 ) {
origFn = fn;
fn = function( event ) {
// Can use an empty set, since event contains the info
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});
},
one: function( types, selector, data, fn ) {
return this.on( types, selector, data, fn, 1 );
},
off: function( types, selector, fn ) {
var handleObj, type;
if ( types && types.preventDefault && types.handleObj ) {
// ( event ) dispatched jQuery.Event
handleObj = types.handleObj;
jQuery( types.delegateTarget ).off(
handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
handleObj.selector,
handleObj.handler
);
return this;
}
if ( typeof types === "object" ) {
// ( types-object [, selector] )
for ( type in types ) {
this.off( type, selector, types[ type ] );
}
return this;
}
if ( selector === false || typeof selector === "function" ) {
// ( types [, fn] )
fn = selector;
selector = undefined;
}
if ( fn === false ) {
fn = returnFalse;
}
return this.each(function() {
jQuery.event.remove( this, types, fn, selector );
});
},
trigger: function( type, data ) {
return this.each(function() {
jQuery.event.trigger( type, data, this );
});
},
triggerHandler: function( type, data ) {
var elem = this[0];
if ( elem ) {
return jQuery.event.trigger( type, data, elem, true );
}
}
});
var isSimple = /^.[^:#\[\.,]*$/,
rparentsprev = /^(?:parents|prev(?:Until|All))/,
rneedsContext = jQuery.expr.match.needsContext,
// methods guaranteed to produce a unique set when starting from a unique set
guaranteedUnique = {
children: true,
contents: true,
next: true,
prev: true
};
jQuery.fn.extend({
find: function( selector ) {
var i,
ret = [],
self = this,
len = self.length;
if ( typeof selector !== "string" ) {
return this.pushStack( jQuery( selector ).filter(function() {
for ( i = 0; i < len; i++ ) {
if ( jQuery.contains( self[ i ], this ) ) {
return true;
}
}
}) );
}
for ( i = 0; i < len; i++ ) {
jQuery.find( selector, self[ i ], ret );
}
// Needed because $( selector, context ) becomes $( context ).find( selector )
ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
ret.selector = this.selector ? this.selector + " " + selector : selector;
return ret;
},
has: function( target ) {
var i,
targets = jQuery( target, this ),
len = targets.length;
return this.filter(function() {
for ( i = 0; i < len; i++ ) {
if ( jQuery.contains( this, targets[i] ) ) {
return true;
}
}
});
},
not: function( selector ) {
return this.pushStack( winnow(this, selector || [], true) );
},
filter: function( selector ) {
return this.pushStack( winnow(this, selector || [], false) );
},
is: function( selector ) {
return !!winnow(
this,
// If this is a positional/relative selector, check membership in the returned set
// so $("p:first").is("p:last") won't return true for a doc with two "p".
typeof selector === "string" && rneedsContext.test( selector ) ?
jQuery( selector ) :
selector || [],
false
).length;
},
closest: function( selectors, context ) {
var cur,
i = 0,
l = this.length,
ret = [],
pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
jQuery( selectors, context || this.context ) :
0;
for ( ; i < l; i++ ) {
for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
// Always skip document fragments
if ( cur.nodeType < 11 && (pos ?
pos.index(cur) > -1 :
// Don't pass non-elements to Sizzle
cur.nodeType === 1 &&
jQuery.find.matchesSelector(cur, selectors)) ) {
cur = ret.push( cur );
break;
}
}
}
return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );
},
// Determine the position of an element within
// the matched set of elements
index: function( elem ) {
// No argument, return index in parent
if ( !elem ) {
return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
}
// index in selector
if ( typeof elem === "string" ) {
return jQuery.inArray( this[0], jQuery( elem ) );
}
// Locate the position of the desired element
return jQuery.inArray(
// If it receives a jQuery object, the first element is used
elem.jquery ? elem[0] : elem, this );
},
add: function( selector, context ) {
var set = typeof selector === "string" ?
jQuery( selector, context ) :
jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
all = jQuery.merge( this.get(), set );
return this.pushStack( jQuery.unique(all) );
},
addBack: function( selector ) {
return this.add( selector == null ?
this.prevObject : this.prevObject.filter(selector)
);
}
});
function sibling( cur, dir ) {
do {
cur = cur[ dir ];
} while ( cur && cur.nodeType !== 1 );
return cur;
}
jQuery.each({
parent: function( elem ) {
var parent = elem.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
},
parents: function( elem ) {
return jQuery.dir( elem, "parentNode" );
},
parentsUntil: function( elem, i, until ) {
return jQuery.dir( elem, "parentNode", until );
},
next: function( elem ) {
return sibling( elem, "nextSibling" );
},
prev: function( elem ) {
return sibling( elem, "previousSibling" );
},
nextAll: function( elem ) {
return jQuery.dir( elem, "nextSibling" );
},
prevAll: function( elem ) {
return jQuery.dir( elem, "previousSibling" );
},
nextUntil: function( elem, i, until ) {
return jQuery.dir( elem, "nextSibling", until );
},
prevUntil: function( elem, i, until ) {
return jQuery.dir( elem, "previousSibling", until );
},
siblings: function( elem ) {
return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
},
children: function( elem ) {
return jQuery.sibling( elem.firstChild );
},
contents: function( elem ) {
return jQuery.nodeName( elem, "iframe" ) ?
elem.contentDocument || elem.contentWindow.document :
jQuery.merge( [], elem.childNodes );
}
}, function( name, fn ) {
jQuery.fn[ name ] = function( until, selector ) {
var ret = jQuery.map( this, fn, until );
if ( name.slice( -5 ) !== "Until" ) {
selector = until;
}
if ( selector && typeof selector === "string" ) {
ret = jQuery.filter( selector, ret );
}
if ( this.length > 1 ) {
// Remove duplicates
if ( !guaranteedUnique[ name ] ) {
ret = jQuery.unique( ret );
}
// Reverse order for parents* and prev-derivatives
if ( rparentsprev.test( name ) ) {
ret = ret.reverse();
}
}
return this.pushStack( ret );
};
});
jQuery.extend({
filter: function( expr, elems, not ) {
var elem = elems[ 0 ];
if ( not ) {
expr = ":not(" + expr + ")";
}
return elems.length === 1 && elem.nodeType === 1 ?
jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
return elem.nodeType === 1;
}));
},
dir: function( elem, dir, until ) {
var matched = [],
cur = elem[ dir ];
while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
if ( cur.nodeType === 1 ) {
matched.push( cur );
}
cur = cur[dir];
}
return matched;
},
sibling: function( n, elem ) {
var r = [];
for ( ; n; n = n.nextSibling ) {
if ( n.nodeType === 1 && n !== elem ) {
r.push( n );
}
}
return r;
}
});
// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
if ( jQuery.isFunction( qualifier ) ) {
return jQuery.grep( elements, function( elem, i ) {
/* jshint -W018 */
return !!qualifier.call( elem, i, elem ) !== not;
});
}
if ( qualifier.nodeType ) {
return jQuery.grep( elements, function( elem ) {
return ( elem === qualifier ) !== not;
});
}
if ( typeof qualifier === "string" ) {
if ( isSimple.test( qualifier ) ) {
return jQuery.filter( qualifier, elements, not );
}
qualifier = jQuery.filter( qualifier, elements );
}
return jQuery.grep( elements, function( elem ) {
return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
});
}
function createSafeFragment( document ) {
var list = nodeNames.split( "|" ),
safeFrag = document.createDocumentFragment();
if ( safeFrag.createElement ) {
while ( list.length ) {
safeFrag.createElement(
list.pop()
);
}
}
return safeFrag;
}
var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
rleadingWhitespace = /^\s+/,
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
rtagName = /<([\w:]+)/,
rtbody = /<tbody/i,
rhtml = /<|&#?\w+;/,
rnoInnerhtml = /<(?:script|style|link)/i,
manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
// checked="checked" or checked
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
rscriptType = /^$|\/(?:java|ecma)script/i,
rscriptTypeMasked = /^true\/(.*)/,
rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
// We have to close these tags to support XHTML (#13200)
wrapMap = {
option: [ 1, "<select multiple='multiple'>", "</select>" ],
legend: [ 1, "<fieldset>", "</fieldset>" ],
area: [ 1, "<map>", "</map>" ],
param: [ 1, "<object>", "</object>" ],
thead: [ 1, "<table>", "</table>" ],
tr: [ 2, "<table><tbody>", "</tbody></table>" ],
col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
// unless wrapped in a div with non-breaking characters in front of it.
_default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>" ]
},
safeFragment = createSafeFragment( document ),
fragmentDiv = safeFragment.appendChild( document.createElement("div") );
wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;
jQuery.fn.extend({
text: function( value ) {
return jQuery.access( this, function( value ) {
return value === undefined ?
jQuery.text( this ) :
this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
}, null, value, arguments.length );
},
append: function() {
return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.appendChild( elem );
}
});
},
prepend: function() {
return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.insertBefore( elem, target.firstChild );
}
});
},
before: function() {
return this.domManip( arguments, function( elem ) {
if ( this.parentNode ) {
this.parentNode.insertBefore( elem, this );
}
});
},
after: function() {
return this.domManip( arguments, function( elem ) {
if ( this.parentNode ) {
this.parentNode.insertBefore( elem, this.nextSibling );
}
});
},
// keepData is for internal use only--do not document
remove: function( selector, keepData ) {
var elem,
elems = selector ? jQuery.filter( selector, this ) : this,
i = 0;
for ( ; (elem = elems[i]) != null; i++ ) {
if ( !keepData && elem.nodeType === 1 ) {
jQuery.cleanData( getAll( elem ) );
}
if ( elem.parentNode ) {
if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
setGlobalEval( getAll( elem, "script" ) );
}
elem.parentNode.removeChild( elem );
}
}
return this;
},
empty: function() {
var elem,
i = 0;
for ( ; (elem = this[i]) != null; i++ ) {
// Remove element nodes and prevent memory leaks
if ( elem.nodeType === 1 ) {
jQuery.cleanData( getAll( elem, false ) );
}
// Remove any remaining nodes
while ( elem.firstChild ) {
elem.removeChild( elem.firstChild );
}
// If this is a select, ensure that it displays empty (#12336)
// Support: IE<9
if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
elem.options.length = 0;
}
}
return this;
},
clone: function( dataAndEvents, deepDataAndEvents ) {
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
return this.map( function () {
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
});
},
html: function( value ) {
return jQuery.access( this, function( value ) {
var elem = this[0] || {},
i = 0,
l = this.length;
if ( value === undefined ) {
return elem.nodeType === 1 ?
elem.innerHTML.replace( rinlinejQuery, "" ) :
undefined;
}
// See if we can take a shortcut and just use innerHTML
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
value = value.replace( rxhtmlTag, "<$1></$2>" );
try {
for (; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
elem = this[i] || {};
if ( elem.nodeType === 1 ) {
jQuery.cleanData( getAll( elem, false ) );
elem.innerHTML = value;
}
}
elem = 0;
// If using innerHTML throws an exception, use the fallback method
} catch(e) {}
}
if ( elem ) {
this.empty().append( value );
}
}, null, value, arguments.length );
},
replaceWith: function() {
var
// Snapshot the DOM in case .domManip sweeps something relevant into its fragment
args = jQuery.map( this, function( elem ) {
return [ elem.nextSibling, elem.parentNode ];
}),
i = 0;
// Make the changes, replacing each context element with the new content
this.domManip( arguments, function( elem ) {
var next = args[ i++ ],
parent = args[ i++ ];
if ( parent ) {
// Don't use the snapshot next if it has moved (#13810)
if ( next && next.parentNode !== parent ) {
next = this.nextSibling;
}
jQuery( this ).remove();
parent.insertBefore( elem, next );
}
// Allow new content to include elements from the context set
}, true );
// Force removal if there was no new content (e.g., from empty arguments)
return i ? this : this.remove();
},
detach: function( selector ) {
return this.remove( selector, true );
},
domManip: function( args, callback, allowIntersection ) {
// Flatten any nested arrays
args = core_concat.apply( [], args );
var first, node, hasScripts,
scripts, doc, fragment,
i = 0,
l = this.length,
set = this,
iNoClone = l - 1,
value = args[0],
isFunction = jQuery.isFunction( value );
// We can't cloneNode fragments that contain checked, in WebKit
if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
return this.each(function( index ) {
var self = set.eq( index );
if ( isFunction ) {
args[0] = value.call( this, index, self.html() );
}
self.domManip( args, callback, allowIntersection );
});
}
if ( l ) {
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
first = fragment.firstChild;
if ( fragment.childNodes.length === 1 ) {
fragment = first;
}
if ( first ) {
scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
hasScripts = scripts.length;
// Use the original fragment for the last item instead of the first because it can end up
// being emptied incorrectly in certain situations (#8070).
for ( ; i < l; i++ ) {
node = fragment;
if ( i !== iNoClone ) {
node = jQuery.clone( node, true, true );
// Keep references to cloned scripts for later restoration
if ( hasScripts ) {
jQuery.merge( scripts, getAll( node, "script" ) );
}
}
callback.call( this[i], node, i );
}
if ( hasScripts ) {
doc = scripts[ scripts.length - 1 ].ownerDocument;
// Reenable scripts
jQuery.map( scripts, restoreScript );
// Evaluate executable scripts on first document insertion
for ( i = 0; i < hasScripts; i++ ) {
node = scripts[ i ];
if ( rscriptType.test( node.type || "" ) &&
!jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
if ( node.src ) {
// Hope ajax is available...
jQuery._evalUrl( node.src );
} else {
jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
}
}
}
}
// Fix #11809: Avoid leaking memory
fragment = first = null;
}
}
return this;
}
});
// Support: IE<8
// Manipulating tables requires a tbody
function manipulationTarget( elem, content ) {
return jQuery.nodeName( elem, "table" ) &&
jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ?
elem.getElementsByTagName("tbody")[0] ||
elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
elem;
}
// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type;
return elem;
}
function restoreScript( elem ) {
var match = rscriptTypeMasked.exec( elem.type );
if ( match ) {
elem.type = match[1];
} else {
elem.removeAttribute("type");
}
return elem;
}
// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
var elem,
i = 0;
for ( ; (elem = elems[i]) != null; i++ ) {
jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) );
}
}
function cloneCopyEvent( src, dest ) {
if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
return;
}
var type, i, l,
oldData = jQuery._data( src ),
curData = jQuery._data( dest, oldData ),
events = oldData.events;
if ( events ) {
delete curData.handle;
curData.events = {};
for ( type in events ) {
for ( i = 0, l = events[ type ].length; i < l; i++ ) {
jQuery.event.add( dest, type, events[ type ][ i ] );
}
}
}
// make the cloned public data object a copy from the original
if ( curData.data ) {
curData.data = jQuery.extend( {}, curData.data );
}
}
function fixCloneNodeIssues( src, dest ) {
var nodeName, e, data;
// We do not need to do anything for non-Elements
if ( dest.nodeType !== 1 ) {
return;
}
nodeName = dest.nodeName.toLowerCase();
// IE6-8 copies events bound via attachEvent when using cloneNode.
if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
data = jQuery._data( dest );
for ( e in data.events ) {
jQuery.removeEvent( dest, e, data.handle );
}
// Event data gets referenced instead of copied if the expando gets copied too
dest.removeAttribute( jQuery.expando );
}
// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
if ( nodeName === "script" && dest.text !== src.text ) {
disableScript( dest ).text = src.text;
restoreScript( dest );
// IE6-10 improperly clones children of object elements using classid.
// IE10 throws NoModificationAllowedError if parent is null, #12132.
} else if ( nodeName === "object" ) {
if ( dest.parentNode ) {
dest.outerHTML = src.outerHTML;
}
// This path appears unavoidable for IE9. When cloning an object
// element in IE9, the outerHTML strategy above is not sufficient.
// If the src has innerHTML and the destination does not,
// copy the src.innerHTML into the dest.innerHTML. #10324
if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
dest.innerHTML = src.innerHTML;
}
} else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
// IE6-8 fails to persist the checked state of a cloned checkbox
// or radio button. Worse, IE6-7 fail to give the cloned element
// a checked appearance if the defaultChecked value isn't also set
dest.defaultChecked = dest.checked = src.checked;
// IE6-7 get confused and end up setting the value of a cloned
// checkbox/radio button to an empty string instead of "on"
if ( dest.value !== src.value ) {
dest.value = src.value;
}
// IE6-8 fails to return the selected option to the default selected
// state when cloning options
} else if ( nodeName === "option" ) {
dest.defaultSelected = dest.selected = src.defaultSelected;
// IE6-8 fails to set the defaultValue to the correct value when
// cloning other types of input fields
} else if ( nodeName === "input" || nodeName === "textarea" ) {
dest.defaultValue = src.defaultValue;
}
}
jQuery.each({
appendTo: "append",
prependTo: "prepend",
insertBefore: "before",
insertAfter: "after",
replaceAll: "replaceWith"
}, function( name, original ) {
jQuery.fn[ name ] = function( selector ) {
var elems,
i = 0,
ret = [],
insert = jQuery( selector ),
last = insert.length - 1;
for ( ; i <= last; i++ ) {
elems = i === last ? this : this.clone(true);
jQuery( insert[i] )[ original ]( elems );
// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
core_push.apply( ret, elems.get() );
}
return this.pushStack( ret );
};
});
function getAll( context, tag ) {
var elems, elem,
i = 0,
found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) :
typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) :
undefined;
if ( !found ) {
for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
if ( !tag || jQuery.nodeName( elem, tag ) ) {
found.push( elem );
} else {
jQuery.merge( found, getAll( elem, tag ) );
}
}
}
return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
jQuery.merge( [ context ], found ) :
found;
}
// Used in buildFragment, fixes the defaultChecked property
function fixDefaultChecked( elem ) {
if ( manipulation_rcheckableType.test( elem.type ) ) {
elem.defaultChecked = elem.checked;
}
}
jQuery.extend({
clone: function( elem, dataAndEvents, deepDataAndEvents ) {
var destElements, node, clone, i, srcElements,
inPage = jQuery.contains( elem.ownerDocument, elem );
if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
clone = elem.cloneNode( true );
// IE<=8 does not properly clone detached, unknown element nodes
} else {
fragmentDiv.innerHTML = elem.outerHTML;
fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
}
if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
destElements = getAll( clone );
srcElements = getAll( elem );
// Fix all IE cloning issues
for ( i = 0; (node = srcElements[i]) != null; ++i ) {
// Ensure that the destination node is not null; Fixes #9587
if ( destElements[i] ) {
fixCloneNodeIssues( node, destElements[i] );
}
}
}
// Copy the events from the original to the clone
if ( dataAndEvents ) {
if ( deepDataAndEvents ) {
srcElements = srcElements || getAll( elem );
destElements = destElements || getAll( clone );
for ( i = 0; (node = srcElements[i]) != null; i++ ) {
cloneCopyEvent( node, destElements[i] );
}
} else {
cloneCopyEvent( elem, clone );
}
}
// Preserve script evaluation history
destElements = getAll( clone, "script" );
if ( destElements.length > 0 ) {
setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
}
destElements = srcElements = node = null;
// Return the cloned set
return clone;
},
buildFragment: function( elems, context, scripts, selection ) {
var j, elem, contains,
tmp, tag, tbody, wrap,
l = elems.length,
// Ensure a safe fragment
safe = createSafeFragment( context ),
nodes = [],
i = 0;
for ( ; i < l; i++ ) {
elem = elems[ i ];
if ( elem || elem === 0 ) {
// Add nodes directly
if ( jQuery.type( elem ) === "object" ) {
jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
// Convert non-html into a text node
} else if ( !rhtml.test( elem ) ) {
nodes.push( context.createTextNode( elem ) );
// Convert html into DOM nodes
} else {
tmp = tmp || safe.appendChild( context.createElement("div") );
// Deserialize a standard representation
tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
wrap = wrapMap[ tag ] || wrapMap._default;
tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];
// Descend through wrappers to the right content
j = wrap[0];
while ( j-- ) {
tmp = tmp.lastChild;
}
// Manually add leading whitespace removed by IE
if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
}
// Remove IE's autoinserted <tbody> from table fragments
if ( !jQuery.support.tbody ) {
// String was a <table>, *may* have spurious <tbody>
elem = tag === "table" && !rtbody.test( elem ) ?
tmp.firstChild :
// String was a bare <thead> or <tfoot>
wrap[1] === "<table>" && !rtbody.test( elem ) ?
tmp :
0;
j = elem && elem.childNodes.length;
while ( j-- ) {
if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
elem.removeChild( tbody );
}
}
}
jQuery.merge( nodes, tmp.childNodes );
// Fix #12392 for WebKit and IE > 9
tmp.textContent = "";
// Fix #12392 for oldIE
while ( tmp.firstChild ) {
tmp.removeChild( tmp.firstChild );
}
// Remember the top-level container for proper cleanup
tmp = safe.lastChild;
}
}
}
// Fix #11356: Clear elements from fragment
if ( tmp ) {
safe.removeChild( tmp );
}
// Reset defaultChecked for any radios and checkboxes
// about to be appended to the DOM in IE 6/7 (#8060)
if ( !jQuery.support.appendChecked ) {
jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
}
i = 0;
while ( (elem = nodes[ i++ ]) ) {
// #4087 - If origin and destination elements are the same, and this is
// that element, do not do anything
if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
continue;
}
contains = jQuery.contains( elem.ownerDocument, elem );
// Append to fragment
tmp = getAll( safe.appendChild( elem ), "script" );
// Preserve script evaluation history
if ( contains ) {
setGlobalEval( tmp );
}
// Capture executables
if ( scripts ) {
j = 0;
while ( (elem = tmp[ j++ ]) ) {
if ( rscriptType.test( elem.type || "" ) ) {
scripts.push( elem );
}
}
}
}
tmp = null;
return safe;
},
cleanData: function( elems, /* internal */ acceptData ) {
var elem, type, id, data,
i = 0,
internalKey = jQuery.expando,
cache = jQuery.cache,
deleteExpando = jQuery.support.deleteExpando,
special = jQuery.event.special;
for ( ; (elem = elems[i]) != null; i++ ) {
if ( acceptData || jQuery.acceptData( elem ) ) {
id = elem[ internalKey ];
data = id && cache[ id ];
if ( data ) {
if ( data.events ) {
for ( type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );
// This is a shortcut to avoid jQuery.event.remove's overhead
} else {
jQuery.removeEvent( elem, type, data.handle );
}
}
}
// Remove cache only if it was not already removed by jQuery.event.remove
if ( cache[ id ] ) {
delete cache[ id ];
// IE does not allow us to delete expando properties from nodes,
// nor does it have a removeAttribute function on Document nodes;
// we must handle all of these cases
if ( deleteExpando ) {
delete elem[ internalKey ];
} else if ( typeof elem.removeAttribute !== core_strundefined ) {
elem.removeAttribute( internalKey );
} else {
elem[ internalKey ] = null;
}
core_deletedIds.push( id );
}
}
}
}
},
_evalUrl: function( url ) {
return jQuery.ajax({
url: url,
type: "GET",
dataType: "script",
async: false,
global: false,
"throws": true
});
}
});
jQuery.fn.extend({
wrapAll: function( html ) {
if ( jQuery.isFunction( html ) ) {
return this.each(function(i) {
jQuery(this).wrapAll( html.call(this, i) );
});
}
if ( this[0] ) {
// The elements to wrap the target around
var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
if ( this[0].parentNode ) {
wrap.insertBefore( this[0] );
}
wrap.map(function() {
var elem = this;
while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
elem = elem.firstChild;
}
return elem;
}).append( this );
}
return this;
},
wrapInner: function( html ) {
if ( jQuery.isFunction( html ) ) {
return this.each(function(i) {
jQuery(this).wrapInner( html.call(this, i) );
});
}
return this.each(function() {
var self = jQuery( this ),
contents = self.contents();
if ( contents.length ) {
contents.wrapAll( html );
} else {
self.append( html );
}
});
},
wrap: function( html ) {
var isFunction = jQuery.isFunction( html );
return this.each(function(i) {
jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
});
},
unwrap: function() {
return this.parent().each(function() {
if ( !jQuery.nodeName( this, "body" ) ) {
jQuery( this ).replaceWith( this.childNodes );
}
}).end();
}
});
var iframe, getStyles, curCSS,
ralpha = /alpha\([^)]*\)/i,
ropacity = /opacity\s*=\s*([^)]*)/,
rposition = /^(top|right|bottom|left)$/,
// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
rmargin = /^margin/,
rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
elemdisplay = { BODY: "block" },
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
cssNormalTransform = {
letterSpacing: 0,
fontWeight: 400
},
cssExpand = [ "Top", "Right", "Bottom", "Left" ],
cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
// return a css property mapped to a potentially vendor prefixed property
function vendorPropName( style, name ) {
// shortcut for names that are not vendor prefixed
if ( name in style ) {
return name;
}
// check for vendor prefixed names
var capName = name.charAt(0).toUpperCase() + name.slice(1),
origName = name,
i = cssPrefixes.length;
while ( i-- ) {
name = cssPrefixes[ i ] + capName;
if ( name in style ) {
return name;
}
}
return origName;
}
function isHidden( elem, el ) {
// isHidden might be called from jQuery#filter function;
// in that case, element will be second argument
elem = el || elem;
return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
}
function showHide( elements, show ) {
var display, elem, hidden,
values = [],
index = 0,
length = elements.length;
for ( ; index < length; index++ ) {
elem = elements[ index ];
if ( !elem.style ) {
continue;
}
values[ index ] = jQuery._data( elem, "olddisplay" );
display = elem.style.display;
if ( show ) {
// Reset the inline display of this element to learn if it is
// being hidden by cascaded rules or not
if ( !values[ index ] && display === "none" ) {
elem.style.display = "";
}
// Set elements which have been overridden with display: none
// in a stylesheet to whatever the default browser style is
// for such an element
if ( elem.style.display === "" && isHidden( elem ) ) {
values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
}
} else {
if ( !values[ index ] ) {
hidden = isHidden( elem );
if ( display && display !== "none" || !hidden ) {
jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
}
}
}
}
// Set the display of most of the elements in a second loop
// to avoid the constant reflow
for ( index = 0; index < length; index++ ) {
elem = elements[ index ];
if ( !elem.style ) {
continue;
}
if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
elem.style.display = show ? values[ index ] || "" : "none";
}
}
return elements;
}
jQuery.fn.extend({
css: function( name, value ) {
return jQuery.access( this, function( elem, name, value ) {
var len, styles,
map = {},
i = 0;
if ( jQuery.isArray( name ) ) {
styles = getStyles( elem );
len = name.length;
for ( ; i < len; i++ ) {
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
}
return map;
}
return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
},
show: function() {
return showHide( this, true );
},
hide: function() {
return showHide( this );
},
toggle: function( state ) {
if ( typeof state === "boolean" ) {
return state ? this.show() : this.hide();
}
return this.each(function() {
if ( isHidden( this ) ) {
jQuery( this ).show();
} else {
jQuery( this ).hide();
}
});
}
});
jQuery.extend({
// Add in style property hooks for overriding the default
// behavior of getting and setting a style property
cssHooks: {
opacity: {
get: function( elem, computed ) {
if ( computed ) {
// We should always get a number back from opacity
var ret = curCSS( elem, "opacity" );
return ret === "" ? "1" : ret;
}
}
}
},
// Don't automatically add "px" to these possibly-unitless properties
cssNumber: {
"columnCount": true,
"fillOpacity": true,
"fontWeight": true,
"lineHeight": true,
"opacity": true,
"order": true,
"orphans": true,
"widows": true,
"zIndex": true,
"zoom": true
},
// Add in properties whose names you wish to fix before
// setting or getting the value
cssProps: {
// normalize float css property
"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
},
// Get and set the style property on a DOM Node
style: function( elem, name, value, extra ) {
// Don't set styles on text and comment nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
return;
}
// Make sure that we're working with the right name
var ret, type, hooks,
origName = jQuery.camelCase( name ),
style = elem.style;
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
// gets hook for the prefixed version
// followed by the unprefixed version
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
// Check if we're setting a value
if ( value !== undefined ) {
type = typeof value;
// convert relative number strings (+= or -=) to relative numbers. #7345
if ( type === "string" && (ret = rrelNum.exec( value )) ) {
value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
// Fixes bug #9237
type = "number";
}
// Make sure that NaN and null values aren't set. See: #7116
if ( value == null || type === "number" && isNaN( value ) ) {
return;
}
// If a number was passed in, add 'px' to the (except for certain CSS properties)
if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
value += "px";
}
// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
// but it would mean to define eight (for every problematic property) identical functions
if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
style[ name ] = "inherit";
}
// If a hook was provided, use that value, otherwise just set the specified value
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
// Fixes bug #5509
try {
style[ name ] = value;
} catch(e) {}
}
} else {
// If a hook was provided get the non-computed value from there
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
return ret;
}
// Otherwise just get the value from the style object
return style[ name ];
}
},
css: function( elem, name, extra, styles ) {
var num, val, hooks,
origName = jQuery.camelCase( name );
// Make sure that we're working with the right name
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
// gets hook for the prefixed version
// followed by the unprefixed version
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
// If a hook was provided get the computed value from there
if ( hooks && "get" in hooks ) {
val = hooks.get( elem, true, extra );
}
// Otherwise, if a way to get the computed value exists, use that
if ( val === undefined ) {
val = curCSS( elem, name, styles );
}
//convert "normal" to computed value
if ( val === "normal" && name in cssNormalTransform ) {
val = cssNormalTransform[ name ];
}
// Return, converting to number if forced or a qualifier was provided and val looks numeric
if ( extra === "" || extra ) {
num = parseFloat( val );
return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
}
return val;
}
});
// NOTE: we've included the "window" in window.getComputedStyle
// because jsdom on node.js will break without it.
if ( window.getComputedStyle ) {
getStyles = function( elem ) {
return window.getComputedStyle( elem, null );
};
curCSS = function( elem, name, _computed ) {
var width, minWidth, maxWidth,
computed = _computed || getStyles( elem ),
// getPropertyValue is only needed for .css('filter') in IE9, see #12537
ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
style = elem.style;
if ( computed ) {
if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
ret = jQuery.style( elem, name );
}
// A tribute to the "awesome hack by Dean Edwards"
// Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
// Remember the original values
width = style.width;
minWidth = style.minWidth;
maxWidth = style.maxWidth;
// Put in the new values to get a computed value out
style.minWidth = style.maxWidth = style.width = ret;
ret = computed.width;
// Revert the changed values
style.width = width;
style.minWidth = minWidth;
style.maxWidth = maxWidth;
}
}
return ret;
};
} else if ( document.documentElement.currentStyle ) {
getStyles = function( elem ) {
return elem.currentStyle;
};
curCSS = function( elem, name, _computed ) {
var left, rs, rsLeft,
computed = _computed || getStyles( elem ),
ret = computed ? computed[ name ] : undefined,
style = elem.style;
// Avoid setting ret to empty string here
// so we don't default to auto
if ( ret == null && style && style[ name ] ) {
ret = style[ name ];
}
// From the awesome hack by Dean Edwards
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
// If we're not dealing with a regular pixel number
// but a number that has a weird ending, we need to convert it to pixels
// but not position css attributes, as those are proportional to the parent element instead
// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
// Remember the original values
left = style.left;
rs = elem.runtimeStyle;
rsLeft = rs && rs.left;
// Put in the new values to get a computed value out
if ( rsLeft ) {
rs.left = elem.currentStyle.left;
}
style.left = name === "fontSize" ? "1em" : ret;
ret = style.pixelLeft + "px";
// Revert the changed values
style.left = left;
if ( rsLeft ) {
rs.left = rsLeft;
}
}
return ret === "" ? "auto" : ret;
};
}
function setPositiveNumber( elem, value, subtract ) {
var matches = rnumsplit.exec( value );
return matches ?
// Guard against undefined "subtract", e.g., when used as in cssHooks
Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
value;
}
function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
var i = extra === ( isBorderBox ? "border" : "content" ) ?
// If we already have the right measurement, avoid augmentation
4 :
// Otherwise initialize for horizontal or vertical properties
name === "width" ? 1 : 0,
val = 0;
for ( ; i < 4; i += 2 ) {
// both box models exclude margin, so add it if we want it
if ( extra === "margin" ) {
val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
}
if ( isBorderBox ) {
// border-box includes padding, so remove it if we want content
if ( extra === "content" ) {
val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
}
// at this point, extra isn't border nor margin, so remove border
if ( extra !== "margin" ) {
val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
}
} else {
// at this point, extra isn't content, so add padding
val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
// at this point, extra isn't content nor padding, so add border
if ( extra !== "padding" ) {
val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
}
}
}
return val;
}
function getWidthOrHeight( elem, name, extra ) {
// Start with offset property, which is equivalent to the border-box value
var valueIsBorderBox = true,
val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
styles = getStyles( elem ),
isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
// some non-html elements return undefined for offsetWidth, so check for null/undefined
// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
if ( val <= 0 || val == null ) {
// Fall back to computed then uncomputed css if necessary
val = curCSS( elem, name, styles );
if ( val < 0 || val == null ) {
val = elem.style[ name ];
}
// Computed unit is not pixels. Stop here and return.
if ( rnumnonpx.test(val) ) {
return val;
}
// we need the check for style in case a browser which returns unreliable values
// for getComputedStyle silently falls back to the reliable elem.style
valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
// Normalize "", auto, and prepare for extra
val = parseFloat( val ) || 0;
}
// use the active box-sizing model to add/subtract irrelevant styles
return ( val +
augmentWidthOrHeight(
elem,
name,
extra || ( isBorderBox ? "border" : "content" ),
valueIsBorderBox,
styles
)
) + "px";
}
// Try to determine the default display value of an element
function css_defaultDisplay( nodeName ) {
var doc = document,
display = elemdisplay[ nodeName ];
if ( !display ) {
display = actualDisplay( nodeName, doc );
// If the simple way fails, read from inside an iframe
if ( display === "none" || !display ) {
// Use the already-created iframe if possible
iframe = ( iframe ||
jQuery("<iframe frameborder='0' width='0' height='0'/>")
.css( "cssText", "display:block !important" )
).appendTo( doc.documentElement );
// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
doc.write("<!doctype html><html><body>");
doc.close();
display = actualDisplay( nodeName, doc );
iframe.detach();
}
// Store the correct default display
elemdisplay[ nodeName ] = display;
}
return display;
}
// Called ONLY from within css_defaultDisplay
function actualDisplay( name, doc ) {
var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
display = jQuery.css( elem[0], "display" );
elem.remove();
return display;
}
jQuery.each([ "height", "width" ], function( i, name ) {
jQuery.cssHooks[ name ] = {
get: function( elem, computed, extra ) {
if ( computed ) {
// certain elements can have dimension info if we invisibly show them
// however, it must have a current display style that would benefit from this
return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
jQuery.swap( elem, cssShow, function() {
return getWidthOrHeight( elem, name, extra );
}) :
getWidthOrHeight( elem, name, extra );
}
},
set: function( elem, value, extra ) {
var styles = extra && getStyles( elem );
return setPositiveNumber( elem, value, extra ?
augmentWidthOrHeight(
elem,
name,
extra,
jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
styles
) : 0
);
}
};
});
if ( !jQuery.support.opacity ) {
jQuery.cssHooks.opacity = {
get: function( elem, computed ) {
// IE uses filters for opacity
return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
computed ? "1" : "";
},
set: function( elem, value ) {
var style = elem.style,
currentStyle = elem.currentStyle,
opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
filter = currentStyle && currentStyle.filter || style.filter || "";
// IE has trouble with opacity if it does not have layout
// Force it by setting the zoom level
style.zoom = 1;
// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
// if value === "", then remove inline opacity #12685
if ( ( value >= 1 || value === "" ) &&
jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
style.removeAttribute ) {
// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
// if "filter:" is present at all, clearType is disabled, we want to avoid this
// style.removeAttribute is IE Only, but so apparently is this code path...
style.removeAttribute( "filter" );
// if there is no filter style applied in a css rule or unset inline opacity, we are done
if ( value === "" || currentStyle && !currentStyle.filter ) {
return;
}
}
// otherwise, set new filter values
style.filter = ralpha.test( filter ) ?
filter.replace( ralpha, opacity ) :
filter + " " + opacity;
}
};
}
// These hooks cannot be added until DOM ready because the support test
// for it is not run until after DOM ready
jQuery(function() {
if ( !jQuery.support.reliableMarginRight ) {
jQuery.cssHooks.marginRight = {
get: function( elem, computed ) {
if ( computed ) {
// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
// Work around by temporarily setting element display to inline-block
return jQuery.swap( elem, { "display": "inline-block" },
curCSS, [ elem, "marginRight" ] );
}
}
};
}
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// getComputedStyle returns percent when specified for top/left/bottom/right
// rather than make the css module depend on the offset module, we just check for it here
if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
jQuery.each( [ "top", "left" ], function( i, prop ) {
jQuery.cssHooks[ prop ] = {
get: function( elem, computed ) {
if ( computed ) {
computed = curCSS( elem, prop );
// if curCSS returns percentage, fallback to offset
return rnumnonpx.test( computed ) ?
jQuery( elem ).position()[ prop ] + "px" :
computed;
}
}
};
});
}
});
if ( jQuery.expr && jQuery.expr.filters ) {
jQuery.expr.filters.hidden = function( elem ) {
// Support: Opera <= 12.12
// Opera reports offsetWidths and offsetHeights less than zero on some elements
return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||
(!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
};
jQuery.expr.filters.visible = function( elem ) {
return !jQuery.expr.filters.hidden( elem );
};
}
// These hooks are used by animate to expand properties
jQuery.each({
margin: "",
padding: "",
border: "Width"
}, function( prefix, suffix ) {
jQuery.cssHooks[ prefix + suffix ] = {
expand: function( value ) {
var i = 0,
expanded = {},
// assumes a single number if not a string
parts = typeof value === "string" ? value.split(" ") : [ value ];
for ( ; i < 4; i++ ) {
expanded[ prefix + cssExpand[ i ] + suffix ] =
parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
}
return expanded;
}
};
if ( !rmargin.test( prefix ) ) {
jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
}
});
var r20 = /%20/g,
rbracket = /\[\]$/,
rCRLF = /\r?\n/g,
rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
rsubmittable = /^(?:input|select|textarea|keygen)/i;
jQuery.fn.extend({
serialize: function() {
return jQuery.param( this.serializeArray() );
},
serializeArray: function() {
return this.map(function(){
// Can add propHook for "elements" to filter or add form elements
var elements = jQuery.prop( this, "elements" );
return elements ? jQuery.makeArray( elements ) : this;
})
.filter(function(){
var type = this.type;
// Use .is(":disabled") so that fieldset[disabled] works
return this.name && !jQuery( this ).is( ":disabled" ) &&
rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
( this.checked || !manipulation_rcheckableType.test( type ) );
})
.map(function( i, elem ){
var val = jQuery( this ).val();
return val == null ?
null :
jQuery.isArray( val ) ?
jQuery.map( val, function( val ){
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
}) :
{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
}).get();
}
});
//Serialize an array of form elements or a set of
//key/values into a query string
jQuery.param = function( a, traditional ) {
var prefix,
s = [],
add = function( key, value ) {
// If value is a function, invoke it and return its value
value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
};
// Set traditional to true for jQuery <= 1.3.2 behavior.
if ( traditional === undefined ) {
traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
}
// If an array was passed in, assume that it is an array of form elements.
if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
// Serialize the form elements
jQuery.each( a, function() {
add( this.name, this.value );
});
} else {
// If traditional, encode the "old" way (the way 1.3.2 or older
// did it), otherwise encode params recursively.
for ( prefix in a ) {
buildParams( prefix, a[ prefix ], traditional, add );
}
}
// Return the resulting serialization
return s.join( "&" ).replace( r20, "+" );
};
function buildParams( prefix, obj, traditional, add ) {
var name;
if ( jQuery.isArray( obj ) ) {
// Serialize array item.
jQuery.each( obj, function( i, v ) {
if ( traditional || rbracket.test( prefix ) ) {
// Treat each array item as a scalar.
add( prefix, v );
} else {
// Item is non-scalar (array or object), encode its numeric index.
buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
}
});
} else if ( !traditional && jQuery.type( obj ) === "object" ) {
// Serialize object item.
for ( name in obj ) {
buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
}
} else {
// Serialize scalar item.
add( prefix, obj );
}
}
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
});
jQuery.fn.extend({
hover: function( fnOver, fnOut ) {
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
},
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
unbind: function( types, fn ) {
return this.off( types, null, fn );
},
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
undelegate: function( selector, types, fn ) {
// ( namespace ) or ( selector, types [, fn] )
return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
}
});
var
// Document location
ajaxLocParts,
ajaxLocation,
ajax_nonce = jQuery.now(),
ajax_rquery = /\?/,
rhash = /#.*$/,
rts = /([?&])_=[^&]*/,
rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
// #7653, #8125, #8152: local protocol detection
rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
rnoContent = /^(?:GET|HEAD)$/,
rprotocol = /^\/\//,
rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
// Keep a copy of the old load method
_load = jQuery.fn.load,
/* Prefilters
* 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
* 2) These are called:
* - BEFORE asking for a transport
* - AFTER param serialization (s.data is a string if s.processData is true)
* 3) key is the dataType
* 4) the catchall symbol "*" can be used
* 5) execution will start with transport dataType and THEN continue down to "*" if needed
*/
prefilters = {},
/* Transports bindings
* 1) key is the dataType
* 2) the catchall symbol "*" can be used
* 3) selection will start with transport dataType and THEN go to "*" if needed
*/
transports = {},
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
allTypes = "*/".concat("*");
// #8138, IE may throw an exception when accessing
// a field from window.location if document.domain has been set
try {
ajaxLocation = location.href;
} catch( e ) {
// Use the href attribute of an A element
// since IE will modify it given document.location
ajaxLocation = document.createElement( "a" );
ajaxLocation.href = "";
ajaxLocation = ajaxLocation.href;
}
// Segment location into parts
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {
// dataTypeExpression is optional and defaults to "*"
return function( dataTypeExpression, func ) {
if ( typeof dataTypeExpression !== "string" ) {
func = dataTypeExpression;
dataTypeExpression = "*";
}
var dataType,
i = 0,
dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
if ( jQuery.isFunction( func ) ) {
// For each dataType in the dataTypeExpression
while ( (dataType = dataTypes[i++]) ) {
// Prepend if requested
if ( dataType[0] === "+" ) {
dataType = dataType.slice( 1 ) || "*";
(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
// Otherwise append
} else {
(structure[ dataType ] = structure[ dataType ] || []).push( func );
}
}
}
};
}
// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
var inspected = {},
seekingTransport = ( structure === transports );
function inspect( dataType ) {
var selected;
inspected[ dataType ] = true;
jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
options.dataTypes.unshift( dataTypeOrTransport );
inspect( dataTypeOrTransport );
return false;
} else if ( seekingTransport ) {
return !( selected = dataTypeOrTransport );
}
});
return selected;
}
return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}
// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
function ajaxExtend( target, src ) {
var deep, key,
flatOptions = jQuery.ajaxSettings.flatOptions || {};
for ( key in src ) {
if ( src[ key ] !== undefined ) {
( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
}
}
if ( deep ) {
jQuery.extend( true, target, deep );
}
return target;
}
jQuery.fn.load = function( url, params, callback ) {
if ( typeof url !== "string" && _load ) {
return _load.apply( this, arguments );
}
var selector, response, type,
self = this,
off = url.indexOf(" ");
if ( off >= 0 ) {
selector = url.slice( off, url.length );
url = url.slice( 0, off );
}
// If it's a function
if ( jQuery.isFunction( params ) ) {
// We assume that it's the callback
callback = params;
params = undefined;
// Otherwise, build a param string
} else if ( params && typeof params === "object" ) {
type = "POST";
}
// If we have elements to modify, make the request
if ( self.length > 0 ) {
jQuery.ajax({
url: url,
// if "type" variable is undefined, then "GET" method will be used
type: type,
dataType: "html",
data: params
}).done(function( responseText ) {
// Save response for use in complete callback
response = arguments;
self.html( selector ?
// If a selector was specified, locate the right elements in a dummy div
// Exclude scripts to avoid IE 'Permission Denied' errors
jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
// Otherwise use the full result
responseText );
}).complete( callback && function( jqXHR, status ) {
self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
});
}
return this;
};
// Attach a bunch of functions for handling common AJAX events
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
jQuery.fn[ type ] = function( fn ){
return this.on( type, fn );
};
});
jQuery.extend({
// Counter for holding the number of active queries
active: 0,
// Last-Modified header cache for next request
lastModified: {},
etag: {},
ajaxSettings: {
url: ajaxLocation,
type: "GET",
isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
global: true,
processData: true,
async: true,
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
/*
timeout: 0,
data: null,
dataType: null,
username: null,
password: null,
cache: null,
throws: false,
traditional: false,
headers: {},
*/
accepts: {
"*": allTypes,
text: "text/plain",
html: "text/html",
xml: "application/xml, text/xml",
json: "application/json, text/javascript"
},
contents: {
xml: /xml/,
html: /html/,
json: /json/
},
responseFields: {
xml: "responseXML",
text: "responseText",
json: "responseJSON"
},
// Data converters
// Keys separate source (or catchall "*") and destination types with a single space
converters: {
// Convert anything to text
"* text": String,
// Text to html (true = no transformation)
"text html": true,
// Evaluate text as a json expression
"text json": jQuery.parseJSON,
// Parse text as xml
"text xml": jQuery.parseXML
},
// For options that shouldn't be deep extended:
// you can add your own custom options here if
// and when you create one that shouldn't be
// deep extended (see ajaxExtend)
flatOptions: {
url: true,
context: true
}
},
// Creates a full fledged settings object into target
// with both ajaxSettings and settings fields.
// If target is omitted, writes into ajaxSettings.
ajaxSetup: function( target, settings ) {
return settings ?
// Building a settings object
ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
// Extending ajaxSettings
ajaxExtend( jQuery.ajaxSettings, target );
},
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
ajaxTransport: addToPrefiltersOrTransports( transports ),
// Main method
ajax: function( url, options ) {
// If url is an object, simulate pre-1.5 signature
if ( typeof url === "object" ) {
options = url;
url = undefined;
}
// Force options to be an object
options = options || {};
var // Cross-domain detection vars
parts,
// Loop variable
i,
// URL without anti-cache param
cacheURL,
// Response headers as string
responseHeadersString,
// timeout handle
timeoutTimer,
// To know if global events are to be dispatched
fireGlobals,
transport,
// Response headers
responseHeaders,
// Create the final options object
s = jQuery.ajaxSetup( {}, options ),
// Callbacks context
callbackContext = s.context || s,
// Context for global events is callbackContext if it is a DOM node or jQuery collection
globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
jQuery( callbackContext ) :
jQuery.event,
// Deferreds
deferred = jQuery.Deferred(),
completeDeferred = jQuery.Callbacks("once memory"),
// Status-dependent callbacks
statusCode = s.statusCode || {},
// Headers (they are sent all at once)
requestHeaders = {},
requestHeadersNames = {},
// The jqXHR state
state = 0,
// Default abort message
strAbort = "canceled",
// Fake xhr
jqXHR = {
readyState: 0,
// Builds headers hashtable if needed
getResponseHeader: function( key ) {
var match;
if ( state === 2 ) {
if ( !responseHeaders ) {
responseHeaders = {};
while ( (match = rheaders.exec( responseHeadersString )) ) {
responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
}
}
match = responseHeaders[ key.toLowerCase() ];
}
return match == null ? null : match;
},
// Raw string
getAllResponseHeaders: function() {
return state === 2 ? responseHeadersString : null;
},
// Caches the header
setRequestHeader: function( name, value ) {
var lname = name.toLowerCase();
if ( !state ) {
name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
requestHeaders[ name ] = value;
}
return this;
},
// Overrides response content-type header
overrideMimeType: function( type ) {
if ( !state ) {
s.mimeType = type;
}
return this;
},
// Status-dependent callbacks
statusCode: function( map ) {
var code;
if ( map ) {
if ( state < 2 ) {
for ( code in map ) {
// Lazy-add the new callback in a way that preserves old ones
statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
}
} else {
// Execute the appropriate callbacks
jqXHR.always( map[ jqXHR.status ] );
}
}
return this;
},
// Cancel the request
abort: function( statusText ) {
var finalText = statusText || strAbort;
if ( transport ) {
transport.abort( finalText );
}
done( 0, finalText );
return this;
}
};
// Attach deferreds
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
// Remove hash character (#7531: and string promotion)
// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
// Handle falsy url in the settings object (#10093: consistency with old signature)
// We also use the url parameter if available
s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
// Alias method option to type as per ticket #12004
s.type = options.method || options.type || s.method || s.type;
// Extract dataTypes list
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
// A cross-domain request is in order when we have a protocol:host:port mismatch
if ( s.crossDomain == null ) {
parts = rurl.exec( s.url.toLowerCase() );
s.crossDomain = !!( parts &&
( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
);
}
// Convert data if not already a string
if ( s.data && s.processData && typeof s.data !== "string" ) {
s.data = jQuery.param( s.data, s.traditional );
}
// Apply prefilters
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
// If request was aborted inside a prefilter, stop there
if ( state === 2 ) {
return jqXHR;
}
// We can fire global events as of now if asked to
fireGlobals = s.global;
// Watch for a new set of requests
if ( fireGlobals && jQuery.active++ === 0 ) {
jQuery.event.trigger("ajaxStart");
}
// Uppercase the type
s.type = s.type.toUpperCase();
// Determine if request has content
s.hasContent = !rnoContent.test( s.type );
// Save the URL in case we're toying with the If-Modified-Since
// and/or If-None-Match header later on
cacheURL = s.url;
// More options handling for requests with no content
if ( !s.hasContent ) {
// If data is available, append data to url
if ( s.data ) {
cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
// #9682: remove data so that it's not used in an eventual retry
delete s.data;
}
// Add anti-cache in url if needed
if ( s.cache === false ) {
s.url = rts.test( cacheURL ) ?
// If there is already a '_' parameter, set its value
cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
// Otherwise add one to the end
cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
}
}
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) {
if ( jQuery.lastModified[ cacheURL ] ) {
jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
}
if ( jQuery.etag[ cacheURL ] ) {
jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
}
}
// Set the correct header, if data is being sent
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
jqXHR.setRequestHeader( "Content-Type", s.contentType );
}
// Set the Accepts header for the server, depending on the dataType
jqXHR.setRequestHeader(
"Accept",
s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
s.accepts[ "*" ]
);
// Check for headers option
for ( i in s.headers ) {
jqXHR.setRequestHeader( i, s.headers[ i ] );
}
// Allow custom headers/mimetypes and early abort
if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
// Abort if not done already and return
return jqXHR.abort();
}
// aborting is no longer a cancellation
strAbort = "abort";
// Install callbacks on deferreds
for ( i in { success: 1, error: 1, complete: 1 } ) {
jqXHR[ i ]( s[ i ] );
}
// Get transport
transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
// If no transport, we auto-abort
if ( !transport ) {
done( -1, "No Transport" );
} else {
jqXHR.readyState = 1;
// Send global event
if ( fireGlobals ) {
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
}
// Timeout
if ( s.async && s.timeout > 0 ) {
timeoutTimer = setTimeout(function() {
jqXHR.abort("timeout");
}, s.timeout );
}
try {
state = 1;
transport.send( requestHeaders, done );
} catch ( e ) {
// Propagate exception as error if not done
if ( state < 2 ) {
done( -1, e );
// Simply rethrow otherwise
} else {
throw e;
}
}
}
// Callback for when everything is done
function done( status, nativeStatusText, responses, headers ) {
var isSuccess, success, error, response, modified,
statusText = nativeStatusText;
// Called once
if ( state === 2 ) {
return;
}
// State is "done" now
state = 2;
// Clear timeout if it exists
if ( timeoutTimer ) {
clearTimeout( timeoutTimer );
}
// Dereference transport for early garbage collection
// (no matter how long the jqXHR object will be used)
transport = undefined;
// Cache response headers
responseHeadersString = headers || "";
// Set readyState
jqXHR.readyState = status > 0 ? 4 : 0;
// Determine if successful
isSuccess = status >= 200 && status < 300 || status === 304;
// Get response data
if ( responses ) {
response = ajaxHandleResponses( s, jqXHR, responses );
}
// Convert no matter what (that way responseXXX fields are always set)
response = ajaxConvert( s, response, jqXHR, isSuccess );
// If successful, handle type chaining
if ( isSuccess ) {
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) {
modified = jqXHR.getResponseHeader("Last-Modified");
if ( modified ) {
jQuery.lastModified[ cacheURL ] = modified;
}
modified = jqXHR.getResponseHeader("etag");
if ( modified ) {
jQuery.etag[ cacheURL ] = modified;
}
}
// if no content
if ( status === 204 || s.type === "HEAD" ) {
statusText = "nocontent";
// if not modified
} else if ( status === 304 ) {
statusText = "notmodified";
// If we have data, let's convert it
} else {
statusText = response.state;
success = response.data;
error = response.error;
isSuccess = !error;
}
} else {
// We extract error from statusText
// then normalize statusText and status for non-aborts
error = statusText;
if ( status || !statusText ) {
statusText = "error";
if ( status < 0 ) {
status = 0;
}
}
}
// Set data for the fake xhr object
jqXHR.status = status;
jqXHR.statusText = ( nativeStatusText || statusText ) + "";
// Success/Error
if ( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}
// Status-dependent callbacks
jqXHR.statusCode( statusCode );
statusCode = undefined;
if ( fireGlobals ) {
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
[ jqXHR, s, isSuccess ? success : error ] );
}
// Complete
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
if ( fireGlobals ) {
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
// Handle the global AJAX counter
if ( !( --jQuery.active ) ) {
jQuery.event.trigger("ajaxStop");
}
}
}
return jqXHR;
},
getJSON: function( url, data, callback ) {
return jQuery.get( url, data, callback, "json" );
},
getScript: function( url, callback ) {
return jQuery.get( url, undefined, callback, "script" );
}
});
jQuery.each( [ "get", "post" ], function( i, method ) {
jQuery[ method ] = function( url, data, callback, type ) {
// shift arguments if data argument was omitted
if ( jQuery.isFunction( data ) ) {
type = type || callback;
callback = data;
data = undefined;
}
return jQuery.ajax({
url: url,
type: method,
dataType: type,
data: data,
success: callback
});
};
});
/* Handles responses to an ajax request:
* - finds the right dataType (mediates between content-type and expected dataType)
* - returns the corresponding response
*/
function ajaxHandleResponses( s, jqXHR, responses ) {
var firstDataType, ct, finalDataType, type,
contents = s.contents,
dataTypes = s.dataTypes;
// Remove auto dataType and get content-type in the process
while( dataTypes[ 0 ] === "*" ) {
dataTypes.shift();
if ( ct === undefined ) {
ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
}
}
// Check if we're dealing with a known content-type
if ( ct ) {
for ( type in contents ) {
if ( contents[ type ] && contents[ type ].test( ct ) ) {
dataTypes.unshift( type );
break;
}
}
}
// Check to see if we have a response for the expected dataType
if ( dataTypes[ 0 ] in responses ) {
finalDataType = dataTypes[ 0 ];
} else {
// Try convertible dataTypes
for ( type in responses ) {
if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
finalDataType = type;
break;
}
if ( !firstDataType ) {
firstDataType = type;
}
}
// Or just use first one
finalDataType = finalDataType || firstDataType;
}
// If we found a dataType
// We add the dataType to the list if needed
// and return the corresponding response
if ( finalDataType ) {
if ( finalDataType !== dataTypes[ 0 ] ) {
dataTypes.unshift( finalDataType );
}
return responses[ finalDataType ];
}
}
/* Chain conversions given the request and the original response
* Also sets the responseXXX fields on the jqXHR instance
*/
function ajaxConvert( s, response, jqXHR, isSuccess ) {
var conv2, current, conv, tmp, prev,
converters = {},
// Work with a copy of dataTypes in case we need to modify it for conversion
dataTypes = s.dataTypes.slice();
// Create converters map with lowercased keys
if ( dataTypes[ 1 ] ) {
for ( conv in s.converters ) {
converters[ conv.toLowerCase() ] = s.converters[ conv ];
}
}
current = dataTypes.shift();
// Convert to each sequential dataType
while ( current ) {
if ( s.responseFields[ current ] ) {
jqXHR[ s.responseFields[ current ] ] = response;
}
// Apply the dataFilter if provided
if ( !prev && isSuccess && s.dataFilter ) {
response = s.dataFilter( response, s.dataType );
}
prev = current;
current = dataTypes.shift();
if ( current ) {
// There's only work to do if current dataType is non-auto
if ( current === "*" ) {
current = prev;
// Convert response if prev dataType is non-auto and differs from current
} else if ( prev !== "*" && prev !== current ) {
// Seek a direct converter
conv = converters[ prev + " " + current ] || converters[ "* " + current ];
// If none found, seek a pair
if ( !conv ) {
for ( conv2 in converters ) {
// If conv2 outputs current
tmp = conv2.split( " " );
if ( tmp[ 1 ] === current ) {
// If prev can be converted to accepted input
conv = converters[ prev + " " + tmp[ 0 ] ] ||
converters[ "* " + tmp[ 0 ] ];
if ( conv ) {
// Condense equivalence converters
if ( conv === true ) {
conv = converters[ conv2 ];
// Otherwise, insert the intermediate dataType
} else if ( converters[ conv2 ] !== true ) {
current = tmp[ 0 ];
dataTypes.unshift( tmp[ 1 ] );
}
break;
}
}
}
}
// Apply converter (if not an equivalence)
if ( conv !== true ) {
// Unless errors are allowed to bubble, catch and return them
if ( conv && s[ "throws" ] ) {
response = conv( response );
} else {
try {
response = conv( response );
} catch ( e ) {
return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
}
}
}
}
}
}
return { state: "success", data: response };
}
// Install script dataType
jQuery.ajaxSetup({
accepts: {
script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
},
contents: {
script: /(?:java|ecma)script/
},
converters: {
"text script": function( text ) {
jQuery.globalEval( text );
return text;
}
}
});
// Handle cache's special case and global
jQuery.ajaxPrefilter( "script", function( s ) {
if ( s.cache === undefined ) {
s.cache = false;
}
if ( s.crossDomain ) {
s.type = "GET";
s.global = false;
}
});
// Bind script tag hack transport
jQuery.ajaxTransport( "script", function(s) {
// This transport only deals with cross domain requests
if ( s.crossDomain ) {
var script,
head = document.head || jQuery("head")[0] || document.documentElement;
return {
send: function( _, callback ) {
script = document.createElement("script");
script.async = true;
if ( s.scriptCharset ) {
script.charset = s.scriptCharset;
}
script.src = s.url;
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function( _, isAbort ) {
if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
// Remove the script
if ( script.parentNode ) {
script.parentNode.removeChild( script );
}
// Dereference the script
script = null;
// Callback if not abort
if ( !isAbort ) {
callback( 200, "success" );
}
}
};
// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
// Use native DOM manipulation to avoid our domManip AJAX trickery
head.insertBefore( script, head.firstChild );
},
abort: function() {
if ( script ) {
script.onload( undefined, true );
}
}
};
}
});
var oldCallbacks = [],
rjsonp = /(=)\?(?=&|$)|\?\?/;
// Default jsonp settings
jQuery.ajaxSetup({
jsonp: "callback",
jsonpCallback: function() {
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
this[ callback ] = true;
return callback;
}
});
// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
var callbackName, overwritten, responseContainer,
jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
"url" :
typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
);
// Handle iff the expected data type is "jsonp" or we have a parameter to set
if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
// Get callback name, remembering preexisting value associated with it
callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
s.jsonpCallback() :
s.jsonpCallback;
// Insert callback into url or form data
if ( jsonProp ) {
s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
} else if ( s.jsonp !== false ) {
s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
}
// Use data converter to retrieve json after script execution
s.converters["script json"] = function() {
if ( !responseContainer ) {
jQuery.error( callbackName + " was not called" );
}
return responseContainer[ 0 ];
};
// force json dataType
s.dataTypes[ 0 ] = "json";
// Install callback
overwritten = window[ callbackName ];
window[ callbackName ] = function() {
responseContainer = arguments;
};
// Clean-up function (fires after converters)
jqXHR.always(function() {
// Restore preexisting value
window[ callbackName ] = overwritten;
// Save back as free
if ( s[ callbackName ] ) {
// make sure that re-using the options doesn't screw things around
s.jsonpCallback = originalSettings.jsonpCallback;
// save the callback name for future use
oldCallbacks.push( callbackName );
}
// Call if it was a function and we have a response
if ( responseContainer && jQuery.isFunction( overwritten ) ) {
overwritten( responseContainer[ 0 ] );
}
responseContainer = overwritten = undefined;
});
// Delegate to script
return "script";
}
});
var xhrCallbacks, xhrSupported,
xhrId = 0,
// #5280: Internet Explorer will keep connections alive if we don't abort on unload
xhrOnUnloadAbort = window.ActiveXObject && function() {
// Abort all pending requests
var key;
for ( key in xhrCallbacks ) {
xhrCallbacks[ key ]( undefined, true );
}
};
// Functions to create xhrs
function createStandardXHR() {
try {
return new window.XMLHttpRequest();
} catch( e ) {}
}
function createActiveXHR() {
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch( e ) {}
}
// Create the request object
// (This is still attached to ajaxSettings for backward compatibility)
jQuery.ajaxSettings.xhr = window.ActiveXObject ?
/* Microsoft failed to properly
* implement the XMLHttpRequest in IE7 (can't request local files),
* so we use the ActiveXObject when it is available
* Additionally XMLHttpRequest can be disabled in IE7/IE8 so
* we need a fallback.
*/
function() {
return !this.isLocal && createStandardXHR() || createActiveXHR();
} :
// For all other browsers, use the standard XMLHttpRequest object
createStandardXHR;
// Determine support properties
xhrSupported = jQuery.ajaxSettings.xhr();
jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
xhrSupported = jQuery.support.ajax = !!xhrSupported;
// Create transport if the browser can provide an xhr
if ( xhrSupported ) {
jQuery.ajaxTransport(function( s ) {
// Cross domain only allowed if supported through XMLHttpRequest
if ( !s.crossDomain || jQuery.support.cors ) {
var callback;
return {
send: function( headers, complete ) {
// Get a new xhr
var handle, i,
xhr = s.xhr();
// Open the socket
// Passing null username, generates a login popup on Opera (#2865)
if ( s.username ) {
xhr.open( s.type, s.url, s.async, s.username, s.password );
} else {
xhr.open( s.type, s.url, s.async );
}
// Apply custom fields if provided
if ( s.xhrFields ) {
for ( i in s.xhrFields ) {
xhr[ i ] = s.xhrFields[ i ];
}
}
// Override mime type if needed
if ( s.mimeType && xhr.overrideMimeType ) {
xhr.overrideMimeType( s.mimeType );
}
// X-Requested-With header
// For cross-domain requests, seeing as conditions for a preflight are
// akin to a jigsaw puzzle, we simply never set it to be sure.
// (it can always be set on a per-request basis or even using ajaxSetup)
// For same-domain requests, won't change header if already provided.
if ( !s.crossDomain && !headers["X-Requested-With"] ) {
headers["X-Requested-With"] = "XMLHttpRequest";
}
// Need an extra try/catch for cross domain requests in Firefox 3
try {
for ( i in headers ) {
xhr.setRequestHeader( i, headers[ i ] );
}
} catch( err ) {}
// Do send the request
// This may raise an exception which is actually
// handled in jQuery.ajax (so no try/catch here)
xhr.send( ( s.hasContent && s.data ) || null );
// Listener
callback = function( _, isAbort ) {
var status, responseHeaders, statusText, responses;
// Firefox throws exceptions when accessing properties
// of an xhr when a network error occurred
// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
try {
// Was never called and is aborted or complete
if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
// Only called once
callback = undefined;
// Do not keep as active anymore
if ( handle ) {
xhr.onreadystatechange = jQuery.noop;
if ( xhrOnUnloadAbort ) {
delete xhrCallbacks[ handle ];
}
}
// If it's an abort
if ( isAbort ) {
// Abort it manually if needed
if ( xhr.readyState !== 4 ) {
xhr.abort();
}
} else {
responses = {};
status = xhr.status;
responseHeaders = xhr.getAllResponseHeaders();
// When requesting binary data, IE6-9 will throw an exception
// on any attempt to access responseText (#11426)
if ( typeof xhr.responseText === "string" ) {
responses.text = xhr.responseText;
}
// Firefox throws an exception when accessing
// statusText for faulty cross-domain requests
try {
statusText = xhr.statusText;
} catch( e ) {
// We normalize with Webkit giving an empty statusText
statusText = "";
}
// Filter status for non standard behaviors
// If the request is local and we have data: assume a success
// (success with no data won't get notified, that's the best we
// can do given current implementations)
if ( !status && s.isLocal && !s.crossDomain ) {
status = responses.text ? 200 : 404;
// IE - #1450: sometimes returns 1223 when it should be 204
} else if ( status === 1223 ) {
status = 204;
}
}
}
} catch( firefoxAccessException ) {
if ( !isAbort ) {
complete( -1, firefoxAccessException );
}
}
// Call complete if needed
if ( responses ) {
complete( status, statusText, responses, responseHeaders );
}
};
if ( !s.async ) {
// if we're in sync mode we fire the callback
callback();
} else if ( xhr.readyState === 4 ) {
// (IE6 & IE7) if it's in cache and has been
// retrieved directly we need to fire the callback
setTimeout( callback );
} else {
handle = ++xhrId;
if ( xhrOnUnloadAbort ) {
// Create the active xhrs callbacks list if needed
// and attach the unload handler
if ( !xhrCallbacks ) {
xhrCallbacks = {};
jQuery( window ).unload( xhrOnUnloadAbort );
}
// Add to list of active xhrs callbacks
xhrCallbacks[ handle ] = callback;
}
xhr.onreadystatechange = callback;
}
},
abort: function() {
if ( callback ) {
callback( undefined, true );
}
}
};
}
});
}
var fxNow, timerId,
rfxtypes = /^(?:toggle|show|hide)$/,
rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
rrun = /queueHooks$/,
animationPrefilters = [ defaultPrefilter ],
tweeners = {
"*": [function( prop, value ) {
var tween = this.createTween( prop, value ),
target = tween.cur(),
parts = rfxnum.exec( value ),
unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
// Starting value computation is required for potential unit mismatches
start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
rfxnum.exec( jQuery.css( tween.elem, prop ) ),
scale = 1,
maxIterations = 20;
if ( start && start[ 3 ] !== unit ) {
// Trust units reported by jQuery.css
unit = unit || start[ 3 ];
// Make sure we update the tween properties later on
parts = parts || [];
// Iteratively approximate from a nonzero starting point
start = +target || 1;
do {
// If previous iteration zeroed out, double until we get *something*
// Use a string for doubling factor so we don't accidentally see scale as unchanged below
scale = scale || ".5";
// Adjust and apply
start = start / scale;
jQuery.style( tween.elem, prop, start + unit );
// Update scale, tolerating zero or NaN from tween.cur()
// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
}
// Update tween properties
if ( parts ) {
start = tween.start = +start || +target || 0;
tween.unit = unit;
// If a +=/-= token was provided, we're doing a relative animation
tween.end = parts[ 1 ] ?
start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+parts[ 2 ];
}
return tween;
}]
};
// Animations created synchronously will run synchronously
function createFxNow() {
setTimeout(function() {
fxNow = undefined;
});
return ( fxNow = jQuery.now() );
}
function createTween( value, prop, animation ) {
var tween,
collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
index = 0,
length = collection.length;
for ( ; index < length; index++ ) {
if ( (tween = collection[ index ].call( animation, prop, value )) ) {
// we're done with this property
return tween;
}
}
}
function Animation( elem, properties, options ) {
var result,
stopped,
index = 0,
length = animationPrefilters.length,
deferred = jQuery.Deferred().always( function() {
// don't match elem in the :animated selector
delete tick.elem;
}),
tick = function() {
if ( stopped ) {
return false;
}
var currentTime = fxNow || createFxNow(),
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
temp = remaining / animation.duration || 0,
percent = 1 - temp,
index = 0,
length = animation.tweens.length;
for ( ; index < length ; index++ ) {
animation.tweens[ index ].run( percent );
}
deferred.notifyWith( elem, [ animation, percent, remaining ]);
if ( percent < 1 && length ) {
return remaining;
} else {
deferred.resolveWith( elem, [ animation ] );
return false;
}
},
animation = deferred.promise({
elem: elem,
props: jQuery.extend( {}, properties ),
opts: jQuery.extend( true, { specialEasing: {} }, options ),
originalProperties: properties,
originalOptions: options,
startTime: fxNow || createFxNow(),
duration: options.duration,
tweens: [],
createTween: function( prop, end ) {
var tween = jQuery.Tween( elem, animation.opts, prop, end,
animation.opts.specialEasing[ prop ] || animation.opts.easing );
animation.tweens.push( tween );
return tween;
},
stop: function( gotoEnd ) {
var index = 0,
// if we are going to the end, we want to run all the tweens
// otherwise we skip this part
length = gotoEnd ? animation.tweens.length : 0;
if ( stopped ) {
return this;
}
stopped = true;
for ( ; index < length ; index++ ) {
animation.tweens[ index ].run( 1 );
}
// resolve when we played the last frame
// otherwise, reject
if ( gotoEnd ) {
deferred.resolveWith( elem, [ animation, gotoEnd ] );
} else {
deferred.rejectWith( elem, [ animation, gotoEnd ] );
}
return this;
}
}),
props = animation.props;
propFilter( props, animation.opts.specialEasing );
for ( ; index < length ; index++ ) {
result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
if ( result ) {
return result;
}
}
jQuery.map( props, createTween, animation );
if ( jQuery.isFunction( animation.opts.start ) ) {
animation.opts.start.call( elem, animation );
}
jQuery.fx.timer(
jQuery.extend( tick, {
elem: elem,
anim: animation,
queue: animation.opts.queue
})
);
// attach callbacks from options
return animation.progress( animation.opts.progress )
.done( animation.opts.done, animation.opts.complete )
.fail( animation.opts.fail )
.always( animation.opts.always );
}
function propFilter( props, specialEasing ) {
var index, name, easing, value, hooks;
// camelCase, specialEasing and expand cssHook pass
for ( index in props ) {
name = jQuery.camelCase( index );
easing = specialEasing[ name ];
value = props[ index ];
if ( jQuery.isArray( value ) ) {
easing = value[ 1 ];
value = props[ index ] = value[ 0 ];
}
if ( index !== name ) {
props[ name ] = value;
delete props[ index ];
}
hooks = jQuery.cssHooks[ name ];
if ( hooks && "expand" in hooks ) {
value = hooks.expand( value );
delete props[ name ];
// not quite $.extend, this wont overwrite keys already present.
// also - reusing 'index' from above because we have the correct "name"
for ( index in value ) {
if ( !( index in props ) ) {
props[ index ] = value[ index ];
specialEasing[ index ] = easing;
}
}
} else {
specialEasing[ name ] = easing;
}
}
}
jQuery.Animation = jQuery.extend( Animation, {
tweener: function( props, callback ) {
if ( jQuery.isFunction( props ) ) {
callback = props;
props = [ "*" ];
} else {
props = props.split(" ");
}
var prop,
index = 0,
length = props.length;
for ( ; index < length ; index++ ) {
prop = props[ index ];
tweeners[ prop ] = tweeners[ prop ] || [];
tweeners[ prop ].unshift( callback );
}
},
prefilter: function( callback, prepend ) {
if ( prepend ) {
animationPrefilters.unshift( callback );
} else {
animationPrefilters.push( callback );
}
}
});
function defaultPrefilter( elem, props, opts ) {
/* jshint validthis: true */
var prop, value, toggle, tween, hooks, oldfire,
anim = this,
orig = {},
style = elem.style,
hidden = elem.nodeType && isHidden( elem ),
dataShow = jQuery._data( elem, "fxshow" );
// handle queue: false promises
if ( !opts.queue ) {
hooks = jQuery._queueHooks( elem, "fx" );
if ( hooks.unqueued == null ) {
hooks.unqueued = 0;
oldfire = hooks.empty.fire;
hooks.empty.fire = function() {
if ( !hooks.unqueued ) {
oldfire();
}
};
}
hooks.unqueued++;
anim.always(function() {
// doing this makes sure that the complete handler will be called
// before this completes
anim.always(function() {
hooks.unqueued--;
if ( !jQuery.queue( elem, "fx" ).length ) {
hooks.empty.fire();
}
});
});
}
// height/width overflow pass
if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
// Make sure that nothing sneaks out
// Record all 3 overflow attributes because IE does not
// change the overflow attribute when overflowX and
// overflowY are set to the same value
opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
// Set display property to inline-block for height/width
// animations on inline elements that are having width/height animated
if ( jQuery.css( elem, "display" ) === "inline" &&
jQuery.css( elem, "float" ) === "none" ) {
// inline-level elements accept inline-block;
// block-level elements need to be inline with layout
if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
style.display = "inline-block";
} else {
style.zoom = 1;
}
}
}
if ( opts.overflow ) {
style.overflow = "hidden";
if ( !jQuery.support.shrinkWrapBlocks ) {
anim.always(function() {
style.overflow = opts.overflow[ 0 ];
style.overflowX = opts.overflow[ 1 ];
style.overflowY = opts.overflow[ 2 ];
});
}
}
// show/hide pass
for ( prop in props ) {
value = props[ prop ];
if ( rfxtypes.exec( value ) ) {
delete props[ prop ];
toggle = toggle || value === "toggle";
if ( value === ( hidden ? "hide" : "show" ) ) {
continue;
}
orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
}
}
if ( !jQuery.isEmptyObject( orig ) ) {
if ( dataShow ) {
if ( "hidden" in dataShow ) {
hidden = dataShow.hidden;
}
} else {
dataShow = jQuery._data( elem, "fxshow", {} );
}
// store state if its toggle - enables .stop().toggle() to "reverse"
if ( toggle ) {
dataShow.hidden = !hidden;
}
if ( hidden ) {
jQuery( elem ).show();
} else {
anim.done(function() {
jQuery( elem ).hide();
});
}
anim.done(function() {
var prop;
jQuery._removeData( elem, "fxshow" );
for ( prop in orig ) {
jQuery.style( elem, prop, orig[ prop ] );
}
});
for ( prop in orig ) {
tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
if ( !( prop in dataShow ) ) {
dataShow[ prop ] = tween.start;
if ( hidden ) {
tween.end = tween.start;
tween.start = prop === "width" || prop === "height" ? 1 : 0;
}
}
}
}
}
function Tween( elem, options, prop, end, easing ) {
return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;
Tween.prototype = {
constructor: Tween,
init: function( elem, options, prop, end, easing, unit ) {
this.elem = elem;
this.prop = prop;
this.easing = easing || "swing";
this.options = options;
this.start = this.now = this.cur();
this.end = end;
this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
},
cur: function() {
var hooks = Tween.propHooks[ this.prop ];
return hooks && hooks.get ?
hooks.get( this ) :
Tween.propHooks._default.get( this );
},
run: function( percent ) {
var eased,
hooks = Tween.propHooks[ this.prop ];
if ( this.options.duration ) {
this.pos = eased = jQuery.easing[ this.easing ](
percent, this.options.duration * percent, 0, 1, this.options.duration
);
} else {
this.pos = eased = percent;
}
this.now = ( this.end - this.start ) * eased + this.start;
if ( this.options.step ) {
this.options.step.call( this.elem, this.now, this );
}
if ( hooks && hooks.set ) {
hooks.set( this );
} else {
Tween.propHooks._default.set( this );
}
return this;
}
};
Tween.prototype.init.prototype = Tween.prototype;
Tween.propHooks = {
_default: {
get: function( tween ) {
var result;
if ( tween.elem[ tween.prop ] != null &&
(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
return tween.elem[ tween.prop ];
}
// passing an empty string as a 3rd parameter to .css will automatically
// attempt a parseFloat and fallback to a string if the parse fails
// so, simple values such as "10px" are parsed to Float.
// complex values such as "rotate(1rad)" are returned as is.
result = jQuery.css( tween.elem, tween.prop, "" );
// Empty strings, null, undefined and "auto" are converted to 0.
return !result || result === "auto" ? 0 : result;
},
set: function( tween ) {
// use step hook for back compat - use cssHook if its there - use .style if its
// available and use plain properties where available
if ( jQuery.fx.step[ tween.prop ] ) {
jQuery.fx.step[ tween.prop ]( tween );
} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
} else {
tween.elem[ tween.prop ] = tween.now;
}
}
}
};
// Support: IE <=9
// Panic based approach to setting things on disconnected nodes
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
set: function( tween ) {
if ( tween.elem.nodeType && tween.elem.parentNode ) {
tween.elem[ tween.prop ] = tween.now;
}
}
};
jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
var cssFn = jQuery.fn[ name ];
jQuery.fn[ name ] = function( speed, easing, callback ) {
return speed == null || typeof speed === "boolean" ?
cssFn.apply( this, arguments ) :
this.animate( genFx( name, true ), speed, easing, callback );
};
});
jQuery.fn.extend({
fadeTo: function( speed, to, easing, callback ) {
// show any hidden elements after setting opacity to 0
return this.filter( isHidden ).css( "opacity", 0 ).show()
// animate to the value specified
.end().animate({ opacity: to }, speed, easing, callback );
},
animate: function( prop, speed, easing, callback ) {
var empty = jQuery.isEmptyObject( prop ),
optall = jQuery.speed( speed, easing, callback ),
doAnimation = function() {
// Operate on a copy of prop so per-property easing won't be lost
var anim = Animation( this, jQuery.extend( {}, prop ), optall );
// Empty animations, or finishing resolves immediately
if ( empty || jQuery._data( this, "finish" ) ) {
anim.stop( true );
}
};
doAnimation.finish = doAnimation;
return empty || optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation );
},
stop: function( type, clearQueue, gotoEnd ) {
var stopQueue = function( hooks ) {
var stop = hooks.stop;
delete hooks.stop;
stop( gotoEnd );
};
if ( typeof type !== "string" ) {
gotoEnd = clearQueue;
clearQueue = type;
type = undefined;
}
if ( clearQueue && type !== false ) {
this.queue( type || "fx", [] );
}
return this.each(function() {
var dequeue = true,
index = type != null && type + "queueHooks",
timers = jQuery.timers,
data = jQuery._data( this );
if ( index ) {
if ( data[ index ] && data[ index ].stop ) {
stopQueue( data[ index ] );
}
} else {
for ( index in data ) {
if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
stopQueue( data[ index ] );
}
}
}
for ( index = timers.length; index--; ) {
if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
timers[ index ].anim.stop( gotoEnd );
dequeue = false;
timers.splice( index, 1 );
}
}
// start the next in the queue if the last step wasn't forced
// timers currently will call their complete callbacks, which will dequeue
// but only if they were gotoEnd
if ( dequeue || !gotoEnd ) {
jQuery.dequeue( this, type );
}
});
},
finish: function( type ) {
if ( type !== false ) {
type = type || "fx";
}
return this.each(function() {
var index,
data = jQuery._data( this ),
queue = data[ type + "queue" ],
hooks = data[ type + "queueHooks" ],
timers = jQuery.timers,
length = queue ? queue.length : 0;
// enable finishing flag on private data
data.finish = true;
// empty the queue first
jQuery.queue( this, type, [] );
if ( hooks && hooks.stop ) {
hooks.stop.call( this, true );
}
// look for any active animations, and finish them
for ( index = timers.length; index--; ) {
if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
timers[ index ].anim.stop( true );
timers.splice( index, 1 );
}
}
// look for any animations in the old queue and finish them
for ( index = 0; index < length; index++ ) {
if ( queue[ index ] && queue[ index ].finish ) {
queue[ index ].finish.call( this );
}
}
// turn off finishing flag
delete data.finish;
});
}
});
// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
var which,
attrs = { height: type },
i = 0;
// if we include width, step value is 1 to do all cssExpand values,
// if we don't include width, step value is 2 to skip over Left and Right
includeWidth = includeWidth? 1 : 0;
for( ; i < 4 ; i += 2 - includeWidth ) {
which = cssExpand[ i ];
attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
}
if ( includeWidth ) {
attrs.opacity = attrs.width = type;
}
return attrs;
}
// Generate shortcuts for custom animations
jQuery.each({
slideDown: genFx("show"),
slideUp: genFx("hide"),
slideToggle: genFx("toggle"),
fadeIn: { opacity: "show" },
fadeOut: { opacity: "hide" },
fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
jQuery.fn[ name ] = function( speed, easing, callback ) {
return this.animate( props, speed, easing, callback );
};
});
jQuery.speed = function( speed, easing, fn ) {
var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
complete: fn || !fn && easing ||
jQuery.isFunction( speed ) && speed,
duration: speed,
easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
};
opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
// normalize opt.queue - true/undefined/null -> "fx"
if ( opt.queue == null || opt.queue === true ) {
opt.queue = "fx";
}
// Queueing
opt.old = opt.complete;
opt.complete = function() {
if ( jQuery.isFunction( opt.old ) ) {
opt.old.call( this );
}
if ( opt.queue ) {
jQuery.dequeue( this, opt.queue );
}
};
return opt;
};
jQuery.easing = {
linear: function( p ) {
return p;
},
swing: function( p ) {
return 0.5 - Math.cos( p*Math.PI ) / 2;
}
};
jQuery.timers = [];
jQuery.fx = Tween.prototype.init;
jQuery.fx.tick = function() {
var timer,
timers = jQuery.timers,
i = 0;
fxNow = jQuery.now();
for ( ; i < timers.length; i++ ) {
timer = timers[ i ];
// Checks the timer has not already been removed
if ( !timer() && timers[ i ] === timer ) {
timers.splice( i--, 1 );
}
}
if ( !timers.length ) {
jQuery.fx.stop();
}
fxNow = undefined;
};
jQuery.fx.timer = function( timer ) {
if ( timer() && jQuery.timers.push( timer ) ) {
jQuery.fx.start();
}
};
jQuery.fx.interval = 13;
jQuery.fx.start = function() {
if ( !timerId ) {
timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
}
};
jQuery.fx.stop = function() {
clearInterval( timerId );
timerId = null;
};
jQuery.fx.speeds = {
slow: 600,
fast: 200,
// Default speed
_default: 400
};
// Back Compat <1.8 extension point
jQuery.fx.step = {};
if ( jQuery.expr && jQuery.expr.filters ) {
jQuery.expr.filters.animated = function( elem ) {
return jQuery.grep(jQuery.timers, function( fn ) {
return elem === fn.elem;
}).length;
};
}
jQuery.fn.offset = function( options ) {
if ( arguments.length ) {
return options === undefined ?
this :
this.each(function( i ) {
jQuery.offset.setOffset( this, options, i );
});
}
var docElem, win,
box = { top: 0, left: 0 },
elem = this[ 0 ],
doc = elem && elem.ownerDocument;
if ( !doc ) {
return;
}
docElem = doc.documentElement;
// Make sure it's not a disconnected DOM node
if ( !jQuery.contains( docElem, elem ) ) {
return box;
}
// If we don't have gBCR, just use 0,0 rather than error
// BlackBerry 5, iOS 3 (original iPhone)
if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
box = elem.getBoundingClientRect();
}
win = getWindow( doc );
return {
top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
};
};
jQuery.offset = {
setOffset: function( elem, options, i ) {
var position = jQuery.css( elem, "position" );
// set position first, in-case top/left are set even on static elem
if ( position === "static" ) {
elem.style.position = "relative";
}
var curElem = jQuery( elem ),
curOffset = curElem.offset(),
curCSSTop = jQuery.css( elem, "top" ),
curCSSLeft = jQuery.css( elem, "left" ),
calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
props = {}, curPosition = {}, curTop, curLeft;
// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
if ( calculatePosition ) {
curPosition = curElem.position();
curTop = curPosition.top;
curLeft = curPosition.left;
} else {
curTop = parseFloat( curCSSTop ) || 0;
curLeft = parseFloat( curCSSLeft ) || 0;
}
if ( jQuery.isFunction( options ) ) {
options = options.call( elem, i, curOffset );
}
if ( options.top != null ) {
props.top = ( options.top - curOffset.top ) + curTop;
}
if ( options.left != null ) {
props.left = ( options.left - curOffset.left ) + curLeft;
}
if ( "using" in options ) {
options.using.call( elem, props );
} else {
curElem.css( props );
}
}
};
jQuery.fn.extend({
position: function() {
if ( !this[ 0 ] ) {
return;
}
var offsetParent, offset,
parentOffset = { top: 0, left: 0 },
elem = this[ 0 ];
// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
if ( jQuery.css( elem, "position" ) === "fixed" ) {
// we assume that getBoundingClientRect is available when computed position is fixed
offset = elem.getBoundingClientRect();
} else {
// Get *real* offsetParent
offsetParent = this.offsetParent();
// Get correct offsets
offset = this.offset();
if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
parentOffset = offsetParent.offset();
}
// Add offsetParent borders
parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
}
// Subtract parent offsets and element margins
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
return {
top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
};
},
offsetParent: function() {
return this.map(function() {
var offsetParent = this.offsetParent || docElem;
while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
offsetParent = offsetParent.offsetParent;
}
return offsetParent || docElem;
});
}
});
// Create scrollLeft and scrollTop methods
jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
var top = /Y/.test( prop );
jQuery.fn[ method ] = function( val ) {
return jQuery.access( this, function( elem, method, val ) {
var win = getWindow( elem );
if ( val === undefined ) {
return win ? (prop in win) ? win[ prop ] :
win.document.documentElement[ method ] :
elem[ method ];
}
if ( win ) {
win.scrollTo(
!top ? val : jQuery( win ).scrollLeft(),
top ? val : jQuery( win ).scrollTop()
);
} else {
elem[ method ] = val;
}
}, method, val, arguments.length, null );
};
});
function getWindow( elem ) {
return jQuery.isWindow( elem ) ?
elem :
elem.nodeType === 9 ?
elem.defaultView || elem.parentWindow :
false;
}
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
// margin is only for outerHeight, outerWidth
jQuery.fn[ funcName ] = function( margin, value ) {
var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
return jQuery.access( this, function( elem, type, value ) {
var doc;
if ( jQuery.isWindow( elem ) ) {
// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
// isn't a whole lot we can do. See pull request at this URL for discussion:
// https://github.com/jquery/jquery/pull/764
return elem.document.documentElement[ "client" + name ];
}
// Get document width or height
if ( elem.nodeType === 9 ) {
doc = elem.documentElement;
// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
return Math.max(
elem.body[ "scroll" + name ], doc[ "scroll" + name ],
elem.body[ "offset" + name ], doc[ "offset" + name ],
doc[ "client" + name ]
);
}
return value === undefined ?
// Get width or height on the element, requesting but not forcing parseFloat
jQuery.css( elem, type, extra ) :
// Set width or height on the element
jQuery.style( elem, type, value, extra );
}, type, chainable ? margin : undefined, chainable, null );
};
});
});
// Limit scope pollution from any deprecated API
// (function() {
// The number of elements contained in the matched element set
jQuery.fn.size = function() {
return this.length;
};
jQuery.fn.andSelf = jQuery.fn.addBack;
// })();
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
// Expose jQuery as module.exports in loaders that implement the Node
// module pattern (including browserify). Do not create the global, since
// the user will be storing it themselves locally, and globals are frowned
// upon in the Node module world.
module.exports = jQuery;
} else {
// Otherwise expose jQuery to the global object as usual
window.jQuery = window.$ = jQuery;
// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function () { return jQuery; } );
}
}
})( window );
/**
* jQuery JSON plugin 2.4.0
*
* @author Brantley Harris, 2009-2011
* @author Timo Tijhof, 2011-2012
* @source This plugin is heavily influenced by MochiKit's serializeJSON, which is
* copyrighted 2005 by Bob Ippolito.
* @source Brantley Harris wrote this plugin. It is based somewhat on the JSON.org
* website's http://www.json.org/json2.js, which proclaims:
* "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
* I uphold.
* @license MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
(function ($) {
'use strict';
var escape = /["\\\x00-\x1f\x7f-\x9f]/g,
meta = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
hasOwn = Object.prototype.hasOwnProperty;
/**
* jQuery.toJSON
* Converts the given argument into a JSON representation.
*
* @param o {Mixed} The json-serializable *thing* to be converted
*
* If an object has a toJSON prototype, that will be used to get the representation.
* Non-integer/string keys are skipped in the object, as are keys that point to a
* function.
*
*/
$.toJSON = typeof JSON === 'object' && JSON.stringify ? JSON.stringify : function (o) {
if (o === null) {
return 'null';
}
var pairs, k, name, val,
type = $.type(o);
if (type === 'undefined') {
return undefined;
}
// Also covers instantiated Number and Boolean objects,
// which are typeof 'object' but thanks to $.type, we
// catch them here. I don't know whether it is right
// or wrong that instantiated primitives are not
// exported to JSON as an {"object":..}.
// We choose this path because that's what the browsers did.
if (type === 'number' || type === 'boolean') {
return String(o);
}
if (type === 'string') {
return $.quoteString(o);
}
if (typeof o.toJSON === 'function') {
return $.toJSON(o.toJSON());
}
if (type === 'date') {
var month = o.getUTCMonth() + 1,
day = o.getUTCDate(),
year = o.getUTCFullYear(),
hours = o.getUTCHours(),
minutes = o.getUTCMinutes(),
seconds = o.getUTCSeconds(),
milli = o.getUTCMilliseconds();
if (month < 10) {
month = '0' + month;
}
if (day < 10) {
day = '0' + day;
}
if (hours < 10) {
hours = '0' + hours;
}
if (minutes < 10) {
minutes = '0' + minutes;
}
if (seconds < 10) {
seconds = '0' + seconds;
}
if (milli < 100) {
milli = '0' + milli;
}
if (milli < 10) {
milli = '0' + milli;
}
return '"' + year + '-' + month + '-' + day + 'T' +
hours + ':' + minutes + ':' + seconds +
'.' + milli + 'Z"';
}
pairs = [];
if ($.isArray(o)) {
for (k = 0; k < o.length; k++) {
pairs.push($.toJSON(o[k]) || 'null');
}
return '[' + pairs.join(',') + ']';
}
// Any other object (plain object, RegExp, ..)
// Need to do typeof instead of $.type, because we also
// want to catch non-plain objects.
if (typeof o === 'object') {
for (k in o) {
// Only include own properties,
// Filter out inherited prototypes
if (hasOwn.call(o, k)) {
// Keys must be numerical or string. Skip others
type = typeof k;
if (type === 'number') {
name = '"' + k + '"';
} else if (type === 'string') {
name = $.quoteString(k);
} else {
continue;
}
type = typeof o[k];
// Invalid values like these return undefined
// from toJSON, however those object members
// shouldn't be included in the JSON string at all.
if (type !== 'function' && type !== 'undefined') {
val = $.toJSON(o[k]);
pairs.push(name + ':' + val);
}
}
}
return '{' + pairs.join(',') + '}';
}
};
/**
* jQuery.evalJSON
* Evaluates a given json string.
*
* @param str {String}
*/
$.evalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
/*jshint evil: true */
return eval('(' + str + ')');
};
/**
* jQuery.secureEvalJSON
* Evals JSON in a way that is *more* secure.
*
* @param str {String}
*/
$.secureEvalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
var filtered =
str
.replace(/\\["\\\/bfnrtu]/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
if (/^[\],:{}\s]*$/.test(filtered)) {
/*jshint evil: true */
return eval('(' + str + ')');
}
throw new SyntaxError('Error parsing JSON, source is not valid.');
};
/**
* jQuery.quoteString
* Returns a string-repr of a string, escaping quotes intelligently.
* Mostly a support function for toJSON.
* Examples:
* >>> jQuery.quoteString('apple')
* "apple"
*
* >>> jQuery.quoteString('"Where are we going?", she asked.')
* "\"Where are we going?\", she asked."
*/
$.quoteString = function (str) {
if (str.match(escape)) {
return '"' + str.replace(escape, function (a) {
var c = meta[a];
if (typeof c === 'string') {
return c;
}
c = a.charCodeAt();
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
}) + '"';
}
return '"' + str + '"';
};
}(jQuery));
/*!
* jQuery Form Plugin
* version: 3.27.0-2013.02.06
* @requires jQuery v1.5 or later
*
* Examples and documentation at: http://malsup.com/jquery/form/
* Project repository: https://github.com/malsup/form
* Dual licensed under the MIT and GPL licenses:
* http://malsup.github.com/mit-license.txt
* http://malsup.github.com/gpl-license-v2.txt
*/
/*global ActiveXObject alert */
;(function($) {
"use strict";
/*
Usage Note:
-----------
Do not use both ajaxSubmit and ajaxForm on the same form. These
functions are mutually exclusive. Use ajaxSubmit if you want
to bind your own submit handler to the form. For example,
$(document).ready(function() {
$('#myForm').on('submit', function(e) {
e.preventDefault(); // <-- important
$(this).ajaxSubmit({
target: '#output'
});
});
});
Use ajaxForm when you want the plugin to manage all the event binding
for you. For example,
$(document).ready(function() {
$('#myForm').ajaxForm({
target: '#output'
});
});
You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
form does not have to exist when you invoke ajaxForm:
$('#myForm').ajaxForm({
delegation: true,
target: '#output'
});
When using ajaxForm, the ajaxSubmit function will be invoked for you
at the appropriate time.
*/
/**
* Feature detection
*/
var feature = {};
feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
feature.formdata = window.FormData !== undefined;
/**
* ajaxSubmit() provides a mechanism for immediately submitting
* an HTML form using AJAX.
*/
$.fn.ajaxSubmit = function(options) {
/*jshint scripturl:true */
// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
if (!this.length) {
log('ajaxSubmit: skipping submit process - no element selected');
return this;
}
var method, action, url, $form = this;
if (typeof options == 'function') {
options = { success: options };
}
method = this.attr('method');
action = this.attr('action');
url = (typeof action === 'string') ? $.trim(action) : '';
url = url || window.location.href || '';
if (url) {
// clean url (don't include hash vaue)
url = (url.match(/^([^#]+)/)||[])[1];
}
options = $.extend(true, {
url: url,
success: $.ajaxSettings.success,
type: method || 'GET',
iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
}, options);
// hook for manipulating the form data before it is extracted;
// convenient for use with rich editors like tinyMCE or FCKEditor
var veto = {};
this.trigger('form-pre-serialize', [this, options, veto]);
if (veto.veto) {
log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
return this;
}
// provide opportunity to alter form data before it is serialized
if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
log('ajaxSubmit: submit aborted via beforeSerialize callback');
return this;
}
var traditional = options.traditional;
if ( traditional === undefined ) {
traditional = $.ajaxSettings.traditional;
}
var elements = [];
var qx, a = this.formToArray(options.semantic, elements);
if (options.data) {
options.extraData = options.data;
qx = $.param(options.data, traditional);
}
// give pre-submit callback an opportunity to abort the submit
if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
log('ajaxSubmit: submit aborted via beforeSubmit callback');
return this;
}
// fire vetoable 'validate' event
this.trigger('form-submit-validate', [a, this, options, veto]);
if (veto.veto) {
log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
return this;
}
var q = $.param(a, traditional);
if (qx) {
q = ( q ? (q + '&' + qx) : qx );
}
if (options.type.toUpperCase() == 'GET') {
options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
options.data = null; // data is null for 'get'
}
else {
options.data = q; // data is the query string for 'post'
}
var callbacks = [];
if (options.resetForm) {
callbacks.push(function() { $form.resetForm(); });
}
if (options.clearForm) {
callbacks.push(function() { $form.clearForm(options.includeHidden); });
}
// perform a load on the target only if dataType is not provided
if (!options.dataType && options.target) {
var oldSuccess = options.success || function(){};
callbacks.push(function(data) {
var fn = options.replaceTarget ? 'replaceWith' : 'html';
$(options.target)[fn](data).each(oldSuccess, arguments);
});
}
else if (options.success) {
callbacks.push(options.success);
}
options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
var context = options.context || this ; // jQuery 1.4+ supports scope context
for (var i=0, max=callbacks.length; i < max; i++) {
callbacks[i].apply(context, [data, status, xhr || $form, $form]);
}
};
// are there files to upload?
// [value] (issue #113), also see comment:
// https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
var fileInputs = $('input[type=file]:enabled[value!=""]', this);
var hasFileInputs = fileInputs.length > 0;
var mp = 'multipart/form-data';
var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
var fileAPI = feature.fileapi && feature.formdata;
log("fileAPI :" + fileAPI);
var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
var jqxhr;
// options.iframe allows user to force iframe mode
// 06-NOV-09: now defaulting to iframe mode if file input is detected
if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
// hack to fix Safari hang (thanks to Tim Molendijk for this)
// see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
if (options.closeKeepAlive) {
$.get(options.closeKeepAlive, function() {
jqxhr = fileUploadIframe(a);
});
}
else {
jqxhr = fileUploadIframe(a);
}
}
else if ((hasFileInputs || multipart) && fileAPI) {
jqxhr = fileUploadXhr(a);
}
else {
jqxhr = $.ajax(options);
}
$form.removeData('jqxhr').data('jqxhr', jqxhr);
// clear element array
for (var k=0; k < elements.length; k++)
elements[k] = null;
// fire 'notify' event
this.trigger('form-submit-notify', [this, options]);
return this;
// utility fn for deep serialization
function deepSerialize(extraData){
var serialized = $.param(extraData).split('&');
var len = serialized.length;
var result = [];
var i, part;
for (i=0; i < len; i++) {
// #252; undo param space replacement
serialized[i] = serialized[i].replace(/\+/g,' ');
part = serialized[i].split('=');
// #278; use array instead of object storage, favoring array serializations
result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]);
}
return result;
}
// XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
function fileUploadXhr(a) {
var formdata = new FormData();
for (var i=0; i < a.length; i++) {
formdata.append(a[i].name, a[i].value);
}
if (options.extraData) {
var serializedData = deepSerialize(options.extraData);
for (i=0; i < serializedData.length; i++)
if (serializedData[i])
formdata.append(serializedData[i][0], serializedData[i][1]);
}
options.data = null;
var s = $.extend(true, {}, $.ajaxSettings, options, {
contentType: false,
processData: false,
cache: false,
type: method || 'POST'
});
if (options.uploadProgress) {
// workaround because jqXHR does not expose upload property
s.xhr = function() {
var xhr = jQuery.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener('progress', function(event) {
var percent = 0;
var position = event.loaded || event.position; /*event.position is deprecated*/
var total = event.total;
if (event.lengthComputable) {
percent = Math.ceil(position / total * 100);
}
options.uploadProgress(event, position, total, percent);
}, false);
}
return xhr;
};
}
s.data = null;
var beforeSend = s.beforeSend;
s.beforeSend = function(xhr, o) {
o.data = formdata;
if(beforeSend)
beforeSend.call(this, xhr, o);
};
return $.ajax(s);
}
// private function for handling file uploads (hat tip to YAHOO!)
function fileUploadIframe(a) {
var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
var useProp = !!$.fn.prop;
var deferred = $.Deferred();
if (a) {
// ensure that every serialized input is still enabled
for (i=0; i < elements.length; i++) {
el = $(elements[i]);
if ( useProp )
el.prop('disabled', false);
else
el.removeAttr('disabled');
}
}
s = $.extend(true, {}, $.ajaxSettings, options);
s.context = s.context || s;
id = 'jqFormIO' + (new Date().getTime());
if (s.iframeTarget) {
$io = $(s.iframeTarget);
n = $io.attr('name');
if (!n)
$io.attr('name', id);
else
id = n;
}
else {
$io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
}
io = $io[0];
xhr = { // mock object
aborted: 0,
responseText: null,
responseXML: null,
status: 0,
statusText: 'n/a',
getAllResponseHeaders: function() {},
getResponseHeader: function() {},
setRequestHeader: function() {},
abort: function(status) {
var e = (status === 'timeout' ? 'timeout' : 'aborted');
log('aborting upload... ' + e);
this.aborted = 1;
try { // #214, #257
if (io.contentWindow.document.execCommand) {
io.contentWindow.document.execCommand('Stop');
}
}
catch(ignore) {}
$io.attr('src', s.iframeSrc); // abort op in progress
xhr.error = e;
if (s.error)
s.error.call(s.context, xhr, e, status);
if (g)
$.event.trigger("ajaxError", [xhr, s, e]);
if (s.complete)
s.complete.call(s.context, xhr, e);
}
};
g = s.global;
// trigger ajax global events so that activity/block indicators work like normal
if (g && 0 === $.active++) {
$.event.trigger("ajaxStart");
}
if (g) {
$.event.trigger("ajaxSend", [xhr, s]);
}
if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
if (s.global) {
$.active--;
}
deferred.reject();
return deferred;
}
if (xhr.aborted) {
deferred.reject();
return deferred;
}
// add submitting element to data if we know it
sub = form.clk;
if (sub) {
n = sub.name;
if (n && !sub.disabled) {
s.extraData = s.extraData || {};
s.extraData[n] = sub.value;
if (sub.type == "image") {
s.extraData[n+'.x'] = form.clk_x;
s.extraData[n+'.y'] = form.clk_y;
}
}
}
var CLIENT_TIMEOUT_ABORT = 1;
var SERVER_ABORT = 2;
function getDoc(frame) {
var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
return doc;
}
// Rails CSRF hack (thanks to Yvan Barthelemy)
var csrf_token = $('meta[name=csrf-token]').attr('content');
var csrf_param = $('meta[name=csrf-param]').attr('content');
if (csrf_param && csrf_token) {
s.extraData = s.extraData || {};
s.extraData[csrf_param] = csrf_token;
}
// take a breath so that pending repaints get some cpu time before the upload starts
function doSubmit() {
// make sure form attrs are set
var t = $form.attr('target'), a = $form.attr('action');
// update form attrs in IE friendly way
form.setAttribute('target',id);
if (!method) {
form.setAttribute('method', 'POST');
}
if (a != s.url) {
form.setAttribute('action', s.url);
}
// ie borks in some cases when setting encoding
if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
$form.attr({
encoding: 'multipart/form-data',
enctype: 'multipart/form-data'
});
}
// support timout
if (s.timeout) {
timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
}
// look for server aborts
function checkState() {
try {
var state = getDoc(io).readyState;
log('state = ' + state);
if (state && state.toLowerCase() == 'uninitialized')
setTimeout(checkState,50);
}
catch(e) {
log('Server abort: ' , e, ' (', e.name, ')');
cb(SERVER_ABORT);
if (timeoutHandle)
clearTimeout(timeoutHandle);
timeoutHandle = undefined;
}
}
// add "extra" data to form if provided in options
var extraInputs = [];
try {
if (s.extraData) {
for (var n in s.extraData) {
if (s.extraData.hasOwnProperty(n)) {
// if using the $.param format that allows for multiple values with the same name
if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
extraInputs.push(
$('<input type="hidden" name="'+s.extraData[n].name+'">').val(s.extraData[n].value)
.appendTo(form)[0]);
} else {
extraInputs.push(
$('<input type="hidden" name="'+n+'">').val(s.extraData[n])
.appendTo(form)[0]);
}
}
}
}
if (!s.iframeTarget) {
// add iframe to doc and submit the form
$io.appendTo('body');
if (io.attachEvent)
io.attachEvent('onload', cb);
else
io.addEventListener('load', cb, false);
}
setTimeout(checkState,15);
// just in case form has element with name/id of 'submit'
var submitFn = document.createElement('form').submit;
submitFn.apply(form);
}
finally {
// reset attrs and remove "extra" input elements
form.setAttribute('action',a);
if(t) {
form.setAttribute('target', t);
} else {
$form.removeAttr('target');
}
$(extraInputs).remove();
}
}
if (s.forceSync) {
doSubmit();
}
else {
setTimeout(doSubmit, 10); // this lets dom updates render
}
var data, doc, domCheckCount = 50, callbackProcessed;
function cb(e) {
if (xhr.aborted || callbackProcessed) {
return;
}
try {
doc = getDoc(io);
}
catch(ex) {
log('cannot access response document: ', ex);
e = SERVER_ABORT;
}
if (e === CLIENT_TIMEOUT_ABORT && xhr) {
xhr.abort('timeout');
deferred.reject(xhr, 'timeout');
return;
}
else if (e == SERVER_ABORT && xhr) {
xhr.abort('server abort');
deferred.reject(xhr, 'error', 'server abort');
return;
}
if (!doc || doc.location.href == s.iframeSrc) {
// response not received yet
if (!timedOut)
return;
}
if (io.detachEvent)
io.detachEvent('onload', cb);
else
io.removeEventListener('load', cb, false);
var status = 'success', errMsg;
try {
if (timedOut) {
throw 'timeout';
}
var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
log('isXml='+isXml);
if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
if (--domCheckCount) {
// in some browsers (Opera) the iframe DOM is not always traversable when
// the onload callback fires, so we loop a bit to accommodate
log('requeing onLoad callback, DOM not available');
setTimeout(cb, 250);
return;
}
// let this fall through because server response could be an empty document
//log('Could not access iframe DOM after mutiple tries.');
//throw 'DOMException: not available';
}
//log('response detected');
var docRoot = doc.body ? doc.body : doc.documentElement;
xhr.responseText = docRoot ? docRoot.innerHTML : null;
xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
if (isXml)
s.dataType = 'xml';
xhr.getResponseHeader = function(header){
var headers = {'content-type': s.dataType};
return headers[header];
};
// support for XHR 'status' & 'statusText' emulation :
if (docRoot) {
xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
}
var dt = (s.dataType || '').toLowerCase();
var scr = /(json|script|text)/.test(dt);
if (scr || s.textarea) {
// see if user embedded response in textarea
var ta = doc.getElementsByTagName('textarea')[0];
if (ta) {
xhr.responseText = ta.value;
// support for XHR 'status' & 'statusText' emulation :
xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
}
else if (scr) {
// account for browsers injecting pre around json response
var pre = doc.getElementsByTagName('pre')[0];
var b = doc.getElementsByTagName('body')[0];
if (pre) {
xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
}
else if (b) {
xhr.responseText = b.textContent ? b.textContent : b.innerText;
}
}
}
else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
xhr.responseXML = toXml(xhr.responseText);
}
try {
data = httpData(xhr, dt, s);
}
catch (e) {
status = 'parsererror';
xhr.error = errMsg = (e || status);
}
}
catch (e) {
log('error caught: ',e);
status = 'error';
xhr.error = errMsg = (e || status);
}
if (xhr.aborted) {
log('upload aborted');
status = null;
}
if (xhr.status) { // we've set xhr.status
status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
}
// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
if (status === 'success') {
if (s.success)
s.success.call(s.context, data, 'success', xhr);
deferred.resolve(xhr.responseText, 'success', xhr);
if (g)
$.event.trigger("ajaxSuccess", [xhr, s]);
}
else if (status) {
if (errMsg === undefined)
errMsg = xhr.statusText;
if (s.error)
s.error.call(s.context, xhr, status, errMsg);
deferred.reject(xhr, 'error', errMsg);
if (g)
$.event.trigger("ajaxError", [xhr, s, errMsg]);
}
if (g)
$.event.trigger("ajaxComplete", [xhr, s]);
if (g && ! --$.active) {
$.event.trigger("ajaxStop");
}
if (s.complete)
s.complete.call(s.context, xhr, status);
callbackProcessed = true;
if (s.timeout)
clearTimeout(timeoutHandle);
// clean up
setTimeout(function() {
if (!s.iframeTarget)
$io.remove();
xhr.responseXML = null;
}, 100);
}
var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
if (window.ActiveXObject) {
doc = new ActiveXObject('Microsoft.XMLDOM');
doc.async = 'false';
doc.loadXML(s);
}
else {
doc = (new DOMParser()).parseFromString(s, 'text/xml');
}
return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
};
var parseJSON = $.parseJSON || function(s) {
/*jslint evil:true */
return window['eval']('(' + s + ')');
};
var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
var ct = xhr.getResponseHeader('content-type') || '',
xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
data = xml ? xhr.responseXML : xhr.responseText;
if (xml && data.documentElement.nodeName === 'parsererror') {
if ($.error)
$.error('parsererror');
}
if (s && s.dataFilter) {
data = s.dataFilter(data, type);
}
if (typeof data === 'string') {
if (type === 'json' || !type && ct.indexOf('json') >= 0) {
data = parseJSON(data);
} else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
$.globalEval(data);
}
}
return data;
};
return deferred;
}
};
/**
* ajaxForm() provides a mechanism for fully automating form submission.
*
* The advantages of using this method instead of ajaxSubmit() are:
*
* 1: This method will include coordinates for <input type="image" /> elements (if the element
* is used to submit the form).
* 2. This method will include the submit element's name/value data (for the element that was
* used to submit the form).
* 3. This method binds the submit() method to the form for you.
*
* The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
* passes the options argument along after properly binding events for submit elements and
* the form itself.
*/
$.fn.ajaxForm = function(options) {
options = options || {};
options.delegation = options.delegation && $.isFunction($.fn.on);
// in jQuery 1.3+ we can fix mistakes with the ready state
if (!options.delegation && this.length === 0) {
var o = { s: this.selector, c: this.context };
if (!$.isReady && o.s) {
log('DOM not ready, queuing ajaxForm');
$(function() {
$(o.s,o.c).ajaxForm(options);
});
return this;
}
// is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
return this;
}
if ( options.delegation ) {
$(document)
.off('submit.form-plugin', this.selector, doAjaxSubmit)
.off('click.form-plugin', this.selector, captureSubmittingElement)
.on('submit.form-plugin', this.selector, options, doAjaxSubmit)
.on('click.form-plugin', this.selector, options, captureSubmittingElement);
return this;
}
return this.ajaxFormUnbind()
.bind('submit.form-plugin', options, doAjaxSubmit)
.bind('click.form-plugin', options, captureSubmittingElement);
};
// private event handlers
function doAjaxSubmit(e) {
/*jshint validthis:true */
var options = e.data;
if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
e.preventDefault();
$(this).ajaxSubmit(options);
}
}
function captureSubmittingElement(e) {
/*jshint validthis:true */
var target = e.target;
var $el = $(target);
if (!($el.is("[type=submit],[type=image]"))) {
// is this a child element of the submit el? (ex: a span within a button)
var t = $el.closest('[type=submit]');
if (t.length === 0) {
return;
}
target = t[0];
}
var form = this;
form.clk = target;
if (target.type == 'image') {
if (e.offsetX !== undefined) {
form.clk_x = e.offsetX;
form.clk_y = e.offsetY;
} else if (typeof $.fn.offset == 'function') {
var offset = $el.offset();
form.clk_x = e.pageX - offset.left;
form.clk_y = e.pageY - offset.top;
} else {
form.clk_x = e.pageX - target.offsetLeft;
form.clk_y = e.pageY - target.offsetTop;
}
}
// clear form vars
setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
}
// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
return this.unbind('submit.form-plugin click.form-plugin');
};
/**
* formToArray() gathers form element data into an array of objects that can
* be passed to any of the following ajax functions: $.get, $.post, or load.
* Each object in the array has both a 'name' and 'value' property. An example of
* an array for a simple login form might be:
*
* [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
*
* It is this array that is passed to pre-submit callback functions provided to the
* ajaxSubmit() and ajaxForm() methods.
*/
$.fn.formToArray = function(semantic, elements) {
var a = [];
if (this.length === 0) {
return a;
}
var form = this[0];
var els = semantic ? form.getElementsByTagName('*') : form.elements;
if (!els) {
return a;
}
var i,j,n,v,el,max,jmax;
for(i=0, max=els.length; i < max; i++) {
el = els[i];
n = el.name;
if (!n) {
continue;
}
if (semantic && form.clk && el.type == "image") {
// handle image inputs on the fly when semantic == true
if(!el.disabled && form.clk == el) {
a.push({name: n, value: $(el).val(), type: el.type });
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
}
continue;
}
v = $.fieldValue(el, true);
if (v && v.constructor == Array) {
if (elements)
elements.push(el);
for(j=0, jmax=v.length; j < jmax; j++) {
a.push({name: n, value: v[j]});
}
}
else if (feature.fileapi && el.type == 'file' && !el.disabled) {
if (elements)
elements.push(el);
var files = el.files;
if (files.length) {
for (j=0; j < files.length; j++) {
a.push({name: n, value: files[j], type: el.type});
}
}
else {
// #180
a.push({ name: n, value: '', type: el.type });
}
}
else if (v !== null && typeof v != 'undefined') {
if (elements)
elements.push(el);
a.push({name: n, value: v, type: el.type, required: el.required});
}
}
if (!semantic && form.clk) {
// input type=='image' are not found in elements array! handle it here
var $input = $(form.clk), input = $input[0];
n = input.name;
if (n && !input.disabled && input.type == 'image') {
a.push({name: n, value: $input.val()});
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
}
}
return a;
};
/**
* Serializes form data into a 'submittable' string. This method will return a string
* in the format: name1=value1&amp;name2=value2
*/
$.fn.formSerialize = function(semantic) {
//hand off to jQuery.param for proper encoding
return $.param(this.formToArray(semantic));
};
/**
* Serializes all field elements in the jQuery object into a query string.
* This method will return a string in the format: name1=value1&amp;name2=value2
*/
$.fn.fieldSerialize = function(successful) {
var a = [];
this.each(function() {
var n = this.name;
if (!n) {
return;
}
var v = $.fieldValue(this, successful);
if (v && v.constructor == Array) {
for (var i=0,max=v.length; i < max; i++) {
a.push({name: n, value: v[i]});
}
}
else if (v !== null && typeof v != 'undefined') {
a.push({name: this.name, value: v});
}
});
//hand off to jQuery.param for proper encoding
return $.param(a);
};
/**
* Returns the value(s) of the element in the matched set. For example, consider the following form:
*
* <form><fieldset>
* <input name="A" type="text" />
* <input name="A" type="text" />
* <input name="B" type="checkbox" value="B1" />
* <input name="B" type="checkbox" value="B2"/>
* <input name="C" type="radio" value="C1" />
* <input name="C" type="radio" value="C2" />
* </fieldset></form>
*
* var v = $('input[type=text]').fieldValue();
* // if no values are entered into the text inputs
* v == ['','']
* // if values entered into the text inputs are 'foo' and 'bar'
* v == ['foo','bar']
*
* var v = $('input[type=checkbox]').fieldValue();
* // if neither checkbox is checked
* v === undefined
* // if both checkboxes are checked
* v == ['B1', 'B2']
*
* var v = $('input[type=radio]').fieldValue();
* // if neither radio is checked
* v === undefined
* // if first radio is checked
* v == ['C1']
*
* The successful argument controls whether or not the field element must be 'successful'
* (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
* The default value of the successful argument is true. If this value is false the value(s)
* for each element is returned.
*
* Note: This method *always* returns an array. If no valid value can be determined the
* array will be empty, otherwise it will contain one or more values.
*/
$.fn.fieldValue = function(successful) {
for (var val=[], i=0, max=this.length; i < max; i++) {
var el = this[i];
var v = $.fieldValue(el, successful);
if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
continue;
}
if (v.constructor == Array)
$.merge(val, v);
else
val.push(v);
}
return val;
};
/**
* Returns the value of the field element.
*/
$.fieldValue = function(el, successful) {
var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
if (successful === undefined) {
successful = true;
}
if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
(t == 'checkbox' || t == 'radio') && !el.checked ||
(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
tag == 'select' && el.selectedIndex == -1)) {
return null;
}
if (tag == 'select') {
var index = el.selectedIndex;
if (index < 0) {
return null;
}
var a = [], ops = el.options;
var one = (t == 'select-one');
var max = (one ? index+1 : ops.length);
for(var i=(one ? index : 0); i < max; i++) {
var op = ops[i];
if (op.selected) {
var v = op.value;
if (!v) { // extra pain for IE...
v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
}
if (one) {
return v;
}
a.push(v);
}
}
return a;
}
return $(el).val();
};
/**
* Clears the form data. Takes the following actions on the form's input fields:
* - input text fields will have their 'value' property set to the empty string
* - select elements will have their 'selectedIndex' property set to -1
* - checkbox and radio inputs will have their 'checked' property set to false
* - inputs of type submit, button, reset, and hidden will *not* be effected
* - button elements will *not* be effected
*/
$.fn.clearForm = function(includeHidden) {
return this.each(function() {
$('input,select,textarea', this).clearFields(includeHidden);
});
};
/**
* Clears the selected form elements.
*/
$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
return this.each(function() {
var t = this.type, tag = this.tagName.toLowerCase();
if (re.test(t) || tag == 'textarea') {
this.value = '';
}
else if (t == 'checkbox' || t == 'radio') {
this.checked = false;
}
else if (tag == 'select') {
this.selectedIndex = -1;
}
else if (t == "file") {
if (/MSIE/.test(navigator.userAgent)) {
$(this).replaceWith($(this).clone());
} else {
$(this).val('');
}
}
else if (includeHidden) {
// includeHidden can be the value true, or it can be a selector string
// indicating a special test; for example:
// $('#myForm').clearForm('.special:hidden')
// the above would clean hidden inputs that have the class of 'special'
if ( (includeHidden === true && /hidden/.test(t)) ||
(typeof includeHidden == 'string' && $(this).is(includeHidden)) )
this.value = '';
}
});
};
/**
* Resets the form data. Causes all form elements to be reset to their original value.
*/
$.fn.resetForm = function() {
return this.each(function() {
// guard against an input with the name of 'reset'
// note that IE reports the reset function as an 'object'
if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
this.reset();
}
});
};
/**
* Enables or disables any matching elements.
*/
$.fn.enable = function(b) {
if (b === undefined) {
b = true;
}
return this.each(function() {
this.disabled = !b;
});
};
/**
* Checks/unchecks any matching checkboxes or radio buttons and
* selects/deselects and matching option elements.
*/
$.fn.selected = function(select) {
if (select === undefined) {
select = true;
}
return this.each(function() {
var t = this.type;
if (t == 'checkbox' || t == 'radio') {
this.checked = select;
}
else if (this.tagName.toLowerCase() == 'option') {
var $sel = $(this).parent('select');
if (select && $sel[0] && $sel[0].type == 'select-one') {
// deselect all other options
$sel.find('option').selected(false);
}
this.selected = select;
}
});
};
// expose debug var
$.fn.ajaxSubmit.debug = false;
// helper fn for console logging
function log() {
if (!$.fn.ajaxSubmit.debug)
return;
var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
if (window.console && window.console.log) {
window.console.log(msg);
}
else if (window.opera && window.opera.postError) {
window.opera.postError(msg);
}
}
})(jQuery);
/**
* jQuery.timers - Timer abstractions for jQuery
* Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
* Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
* Date: 2009/10/16
*
* @author Blair Mitchelmore
* @version 1.2
*
**/
jQuery.fn.extend({
everyTime: function(interval, label, fn, times) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, times);
});
},
oneTime: function(interval, label, fn) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, 1);
});
},
stopTime: function(label, fn) {
return this.each(function() {
jQuery.timer.remove(this, label, fn);
});
}
});
jQuery.extend({
timer: {
global: [],
guid: 1,
dataKey: "jQuery.timer",
regex: /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/,
powers: {
// Yeah this is major overkill...
'ms': 1,
'cs': 10,
'ds': 100,
's': 1000,
'das': 10000,
'hs': 100000,
'ks': 1000000
},
timeParse: function(value) {
if (value == undefined || value == null)
return null;
var result = this.regex.exec(jQuery.trim(value.toString()));
if (result[2]) {
var num = parseFloat(result[1]);
var mult = this.powers[result[2]] || 1;
return num * mult;
} else {
return value;
}
},
add: function(element, interval, label, fn, times) {
var counter = 0;
if (jQuery.isFunction(label)) {
if (!times)
times = fn;
fn = label;
label = interval;
}
interval = jQuery.timer.timeParse(interval);
if (typeof interval != 'number' || isNaN(interval) || interval < 0)
return;
if (typeof times != 'number' || isNaN(times) || times < 0)
times = 0;
times = times || 0;
var timers = jQuery.data(element, this.dataKey) || jQuery.data(element, this.dataKey, {});
if (!timers[label])
timers[label] = {};
fn.timerID = fn.timerID || this.guid++;
var handler = function() {
if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
jQuery.timer.remove(element, label, fn);
};
handler.timerID = fn.timerID;
if (!timers[label][fn.timerID])
timers[label][fn.timerID] = window.setInterval(handler,interval);
this.global.push( element );
},
remove: function(element, label, fn) {
var timers = jQuery.data(element, this.dataKey), ret;
if ( timers ) {
if (!label) {
for ( label in timers )
this.remove(element, label, fn);
} else if ( timers[label] ) {
if ( fn ) {
if ( fn.timerID ) {
window.clearInterval(timers[label][fn.timerID]);
delete timers[label][fn.timerID];
}
} else {
for ( var fn in timers[label] ) {
window.clearInterval(timers[label][fn]);
delete timers[label][fn];
}
}
for ( ret in timers[label] ) break;
if ( !ret ) {
ret = null;
delete timers[label];
}
}
for ( ret in timers ) break;
if ( !ret )
jQuery.removeData(element, this.dataKey);
}
}
}
});
jQuery(window).bind("unload", function() {
jQuery.each(jQuery.timer.global, function(index, item) {
jQuery.timer.remove(item);
});
});
/*! http://mths.be/placeholder v2.0.7 by @mathias */
;(function(window, document, $) {
var isInputSupported = 'placeholder' in document.createElement('input'),
isTextareaSupported = 'placeholder' in document.createElement('textarea'),
prototype = $.fn,
valHooks = $.valHooks,
hooks,
placeholder;
if (isInputSupported && isTextareaSupported) {
placeholder = prototype.placeholder = function() {
return this;
};
placeholder.input = placeholder.textarea = true;
} else {
placeholder = prototype.placeholder = function() {
var $this = this;
$this
.filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
.not('.placeholder')
.bind({
'focus.placeholder': clearPlaceholder,
'blur.placeholder': setPlaceholder
})
.data('placeholder-enabled', true)
.trigger('blur.placeholder');
return $this;
};
placeholder.input = isInputSupported;
placeholder.textarea = isTextareaSupported;
hooks = {
'get': function(element) {
var $element = $(element);
return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value;
},
'set': function(element, value) {
var $element = $(element);
if (!$element.data('placeholder-enabled')) {
return element.value = value;
}
if (value == '') {
element.value = value;
// Issue #56: Setting the placeholder causes problems if the element continues to have focus.
if (element != document.activeElement) {
// We can't use `triggerHandler` here because of dummy text/password inputs :(
setPlaceholder.call(element);
}
} else if ($element.hasClass('placeholder')) {
clearPlaceholder.call(element, true, value) || (element.value = value);
} else {
element.value = value;
}
// `set` can not return `undefined`; see http://jsapi.info/jquery/1.7.1/val#L2363
return $element;
}
};
isInputSupported || (valHooks.input = hooks);
isTextareaSupported || (valHooks.textarea = hooks);
$(function() {
// Look for forms
$(document).delegate('form', 'submit.placeholder', function() {
// Clear the placeholder values so they don't get submitted
var $inputs = $('.placeholder', this).each(clearPlaceholder);
setTimeout(function() {
$inputs.each(setPlaceholder);
}, 10);
});
});
// Clear placeholder values upon page reload
$(window).bind('beforeunload.placeholder', function() {
$('.placeholder').each(function() {
this.value = '';
});
});
}
function args(elem) {
// Return an object of element attributes
var newAttrs = {},
rinlinejQuery = /^jQuery\d+$/;
$.each(elem.attributes, function(i, attr) {
if (attr.specified && !rinlinejQuery.test(attr.name)) {
newAttrs[attr.name] = attr.value;
}
});
return newAttrs;
}
function clearPlaceholder(event, value) {
var input = this,
$input = $(input);
if (input.value == $input.attr('placeholder') && $input.hasClass('placeholder')) {
if ($input.data('placeholder-password')) {
$input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id'));
// If `clearPlaceholder` was called from `$.valHooks.input.set`
if (event === true) {
return $input[0].value = value;
}
$input.focus();
} else {
input.value = '';
$input.removeClass('placeholder');
input == document.activeElement && input.select();
}
}
}
function setPlaceholder() {
var $replacement,
input = this,
$input = $(input),
$origInput = $input,
id = this.id;
if (input.value == '') {
if (input.type == 'password') {
if (!$input.data('placeholder-textinput')) {
try {
$replacement = $input.clone().attr({ 'type': 'text' });
} catch(e) {
$replacement = $('<input>').attr($.extend(args(this), { 'type': 'text' }));
}
$replacement
.removeAttr('name')
.data({
'placeholder-password': true,
'placeholder-id': id
})
.bind('focus.placeholder', clearPlaceholder);
$input
.data({
'placeholder-textinput': $replacement,
'placeholder-id': id
})
.before($replacement);
}
$input = $input.removeAttr('id').hide().prev().attr('id', id).show();
// Note: `$input[0] != input` now!
}
$input.addClass('placeholder');
$input[0].value = $input.attr('placeholder');
} else {
$input.removeClass('placeholder');
}
}
}(this, document, jQuery));
$.fn.selectRange = function(start, end) {
return this.each(function() {
if (this.setSelectionRange) {
this.focus();
this.setSelectionRange(start, end);
} else if (this.createTextRange) {
var range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
range.select();
}
});
};
// License: PD
// This code was written by Tyler Akins and has been placed in the
// public domain. It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
var Base64 = (function () {
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var obj = {
/**
* Encodes a string in base64
* @param {String} input The string to encode in base64.
*/
encode: function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
keyStr.charAt(enc3) + keyStr.charAt(enc4);
} while (i < input.length);
return output;
},
/**
* Decodes a base64 string.
* @param {String} input The string to decode.
*/
decode: function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
enc1 = keyStr.indexOf(input.charAt(i++));
enc2 = keyStr.indexOf(input.charAt(i++));
enc3 = keyStr.indexOf(input.charAt(i++));
enc4 = keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
} while (i < input.length);
return output;
}
};
return obj;
})();
/*
Jappix - An open social platform
This is the JSJaC library for Jappix (from trunk)
-------------------------------------------------
Licenses: Mozilla Public License version 1.1, GNU GPL, AGPL
Authors: Stefan Strigler, Valérian Saliou, Zash, Maranda
Last revision: 07/07/13
*/
/**
* @fileoverview Magic dependency loading. Taken from script.aculo.us
* and modified to break it.
* @author Stefan Strigler steve@zeank.in-berlin.de
* @version $Revision$
*/
var JSJaC = {
Version: '$Rev$',
bind: function(fn, obj, optArg) {
return function(arg) {
return fn.apply(obj, [arg, optArg]);
};
}
};
if (typeof JSJaCConnection == 'undefined')
JSJaC.load();
/* Copyright 2006 Erik Arvidsson
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
/**
* @fileoverview Wrapper to make working with XmlHttpRequest and the
* DOM more convenient (cross browser compliance).
* this code is taken from
* http://webfx.eae.net/dhtml/xmlextras/xmlextras.html
* @author Stefan Strigler steve@zeank.in-berlin.de
* @version $Revision$
*/
/**
* XmlHttp factory
* @private
*/
function XmlHttp() {}
/**
* creates a cross browser compliant XmlHttpRequest object
*/
XmlHttp.create = function () {
try {
// Are we cross-domain?
if(!BOSH_SAME_ORIGIN) {
// Able to use CORS?
if (window.XMLHttpRequest) {
var req = new XMLHttpRequest();
if (req.withCredentials !== undefined)
return req;
}
// Fallback on JSONP
return new jXHR();
}
// Might be local-domain?
if (window.XMLHttpRequest) {
var req = new XMLHttpRequest();
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (req.readyState == null) {
req.readyState = 1;
req.addEventListener("load", function () {
req.readyState = 4;
if (typeof req.onreadystatechange == "function")
req.onreadystatechange();
}, false);
}
return req;
}
if (window.ActiveXObject) {
return new ActiveXObject(XmlHttp.getPrefix() + ".XmlHttp");
}
}
catch (ex) {}
// fell through
throw new Error("Your browser does not support XmlHttp objects");
};
/**
* used to find the Automation server name
* @private
*/
XmlHttp.getPrefix = function() {
if (XmlHttp.prefix) // I know what you did last summer
return XmlHttp.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".XmlHttp");
return XmlHttp.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
};
/**
* XmlDocument factory
* @private
*/
function XmlDocument() {}
XmlDocument.create = function (name,ns) {
name = name || 'foo';
ns = ns || '';
try {
var doc;
// DOM2
if (document.implementation && document.implementation.createDocument) {
doc = document.implementation.createDocument(ns, name, null);
// some versions of Moz do not support the readyState property
// and the onreadystate event so we patch it!
if (doc.readyState == null) {
doc.readyState = 1;
doc.addEventListener("load", function () {
doc.readyState = 4;
if (typeof doc.onreadystatechange == "function")
doc.onreadystatechange();
}, false);
}
} else if (window.ActiveXObject) {
doc = new ActiveXObject(XmlDocument.getPrefix() + ".DomDocument");
}
if (!doc.documentElement || doc.documentElement.tagName != name ||
(doc.documentElement.namespaceURI &&
doc.documentElement.namespaceURI != ns)) {
try {
if (ns != '')
doc.appendChild(doc.createElement(name)).
setAttribute('xmlns',ns);
else
doc.appendChild(doc.createElement(name));
} catch (dex) {
doc = document.implementation.createDocument(ns,name,null);
if (doc.documentElement == null)
doc.appendChild(doc.createElement(name));
// fix buggy opera 8.5x
if (ns != '' &&
doc.documentElement.getAttribute('xmlns') != ns) {
doc.documentElement.setAttribute('xmlns',ns);
}
}
}
return doc;
}
catch (ex) { }
throw new Error("Your browser does not support XmlDocument objects");
};
/**
* used to find the Automation server name
* @private
*/
XmlDocument.getPrefix = function() {
if (XmlDocument.prefix)
return XmlDocument.prefix;
var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
var o;
for (var i = 0; i < prefixes.length; i++) {
try {
// try to create the objects
o = new ActiveXObject(prefixes[i] + ".DomDocument");
return XmlDocument.prefix = prefixes[i];
}
catch (ex) {};
}
throw new Error("Could not find an installed XML parser");
};
// Create the loadXML method
if (typeof(Document) != 'undefined' && window.DOMParser) {
/**
* XMLDocument did not extend the Document interface in some
* versions of Mozilla.
* @private
*/
Document.prototype.loadXML = function (s) {
// parse the string to a new doc
var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
// remove all initial children
while (this.hasChildNodes())
this.removeChild(this.lastChild);
// insert and import nodes
for (var i = 0; i < doc2.childNodes.length; i++) {
this.appendChild(this.importNode(doc2.childNodes[i], true));
}
};
}
// Create xml getter for Mozilla
if (window.XMLSerializer &&
window.Node && Node.prototype && Node.prototype.__defineGetter__) {
/**
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage: var sXml = oNode.xml
* @deprecated
* @private
*/
// XMLDocument did not extend the Document interface in some versions
// of Mozilla. Extend both!
XMLDocument.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
/**
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage: var sXml = oNode.xml
* @deprecated
* @private
*/
Document.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
/**
* xml getter
*
* This serializes the DOM tree to an XML String
*
* Usage: var sXml = oNode.xml
* @deprecated
* @private
*/
Node.prototype.__defineGetter__("xml", function () {
return (new XMLSerializer()).serializeToString(this);
});
}
/**
* @fileoverview Collection of functions to make live easier
* @author Stefan Strigler
* @version $Revision$
*/
/**
* Convert special chars to HTML entities
* @addon
* @return The string with chars encoded for HTML
* @type String
*/
String.prototype.htmlEnc = function() {
if(!this)
return this;
return this.replace(/&(?!(amp|apos|gt|lt|quot);)/g,"&amp;")
.replace(/</g,"&lt;")
.replace(/>/g,"&gt;")
.replace(/\'/g,"&apos;")
.replace(/\"/g,"&quot;")
.replace(/\n/g,"<br />");
};
/**
* Convert HTML entities to special chars
* @addon
* @return The normal string
* @type String
*/
String.prototype.revertHtmlEnc = function() {
if(!this)
return this;
var str = this.replace(/&amp;/gi,'&');
str = str.replace(/&lt;/gi,'<');
str = str.replace(/&gt;/gi,'>');
str = str.replace(/&apos;/gi,'\'');
str = str.replace(/&quot;/gi,'\"');
str = str.replace(/<br( )?(\/)?>/gi,'\n');
return str;
};
/**
* Converts from jabber timestamps to JavaScript Date objects
* @addon
* @param {String} ts A string representing a jabber datetime timestamp as
* defined by {@link http://www.xmpp.org/extensions/xep-0082.html XEP-0082}
* @return A javascript Date object corresponding to the jabber DateTime given
* @type Date
*/
Date.jab2date = function(ts) {
// Timestamp
if(!isNaN(ts))
return new Date(ts * 1000);
// Get the UTC date
var date = new Date(Date.UTC(ts.substr(0,4),ts.substr(5,2)-1,ts.substr(8,2),ts.substr(11,2),ts.substr(14,2),ts.substr(17,2)));
if (ts.substr(ts.length-6,1) != 'Z') { // there's an offset
var date_offset = date.getTimezoneOffset() * 60 * 1000;
var offset = new Date();
offset.setTime(0);
offset.setUTCHours(ts.substr(ts.length-5,2));
offset.setUTCMinutes(ts.substr(ts.length-2,2));
if (ts.substr(ts.length-6,1) == '+')
date.setTime(date.getTime() + offset.getTime() + date_offset);
else if (ts.substr(ts.length-6,1) == '-')
date.setTime(date.getTime() - offset.getTime() + date_offset);
}
return date;
};
/**
* Takes a timestamp in the form of 2004-08-13T12:07:04+02:00 as argument
* and converts it to some sort of humane readable format
* @addon
*/
Date.hrTime = function(ts) {
return Date.jab2date(ts).toLocaleString();
};
/**
* somewhat opposit to {@link #hrTime}
* expects a javascript Date object as parameter and returns a jabber
* date string conforming to
* {@link http://www.xmpp.org/extensions/xep-0082.html XEP-0082}
* @see #hrTime
* @return The corresponding jabber DateTime string
* @type String
*/
Date.prototype.jabberDate = function() {
var padZero = function(i) {
if (i < 10) return "0" + i;
return i;
};
var jDate = this.getUTCFullYear() + "-";
jDate += padZero(this.getUTCMonth()+1) + "-";
jDate += padZero(this.getUTCDate()) + "T";
jDate += padZero(this.getUTCHours()) + ":";
jDate += padZero(this.getUTCMinutes()) + ":";
jDate += padZero(this.getUTCSeconds()) + "Z";
return jDate;
};
/**
* Determines the maximum of two given numbers
* @addon
* @param {Number} A a number
* @param {Number} B another number
* @return the maximum of A and B
* @type Number
*/
Number.max = function(A, B) {
return (A > B)? A : B;
};
Number.min = function(A, B) {
return (A < B)? A : B;
};
/* Copyright (c) 1998 - 2007, Paul Johnston & Contributors
* 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 the author 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 THE
* COPYRIGHT OWNER OR CONTRIBUTORS 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.
*
*/
/**
* @fileoverview Collection of MD5 and SHA1 hashing and encoding
* methods.
* @author Stefan Strigler steve@zeank.in-berlin.de
*/
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
/*
* Perform a simple self-test to see if the VM is working
*/
function sha1_vm_test()
{
return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = new Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
var i, j, t, olda, oldb, oldc, oldd, olde;
for (i = 0; i < x.length; i += 16)
{
olda = a;
oldb = b;
oldc = c;
oldd = d;
olde = e;
for (j = 0; j < 80; j++)
{
if (j < 16) { w[j] = x[i + j]; }
else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); }
t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
safe_add(safe_add(e, w[j]), sha1_kt(j)));
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return [a, b, c, d, e];
}
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function sha1_ft(t, b, c, d)
{
if (t < 20) { return (b & c) | ((~b) & d); }
if (t < 40) { return b ^ c ^ d; }
if (t < 60) { return (b & c) | (b & d) | (c & d); }
return b ^ c ^ d;
}
/*
* Determine the appropriate additive constant for the current iteration
*/
function sha1_kt(t)
{
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
(t < 60) ? -1894007588 : -899497514;
}
/*
* Calculate the HMAC-SHA1 of a key and some data
*/
function core_hmac_sha1(key, data)
{
var bkey = str2binb(key);
if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * chrsz); }
var ipad = new Array(16), opad = new Array(16);
for (var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
return core_sha1(opad.concat(hash), 512 + 160);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str)
{
var bin = [];
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz)
{
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
}
return bin;
}
/*
* Convert an array of big-endian words to a string
*/
function binb2str(bin)
{
var str = "";
var mask = (1 << chrsz) - 1;
for (var i = 0; i < bin.length * 32; i += chrsz)
{
str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
}
return str;
}
/*
* Convert an array of big-endian words to a hex string.
*/
function binb2hex(binarray)
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
}
return str;
}
/*
* Convert an array of big-endian words to a base-64 string
*/
function binb2b64(binarray)
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
var triplet, j;
for (var i = 0; i < binarray.length * 4; i += 3)
{
triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) |
(((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) |
((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
for (j = 0; j < 4; j++)
{
if (i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); }
}
}
return str;
}
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s)
{
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data)
{
var bkey = rstr2binl(key);
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input)
{
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
}
/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input)
{
try { b64pad } catch(e) { b64pad=''; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for(var i = 0; i < len; i += 3)
{
var triplet = (input.charCodeAt(i) << 16)
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}
/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding)
{
var divisor = encoding.length;
var i, j, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++)
{
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
var full_length = Math.ceil(input.length * 8 /
(Math.log(encoding.length) / Math.log(2)));
var remainders = Array(full_length);
for(j = 0; j < full_length; j++)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
output += encoding.charAt(remainders[i]);
return output;
}
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F)
output += String.fromCharCode(x);
else if(x <= 0x7FF)
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
else if(x <= 0xFFFF)
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}
/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}
function str2rstr_utf16be(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
input.charCodeAt(i) & 0xFF);
return output;
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input)
{
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
return output;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
/* #############################################################################
UTF-8 Decoder and Encoder
base64 Encoder and Decoder
written by Tobias Kieslich, justdreams
Contact: tobias@justdreams.de http://www.justdreams.de/
############################################################################# */
// returns an array of byterepresenting dezimal numbers which represent the
// plaintext in an UTF-8 encoded version. Expects a string.
// This function includes an exception management for those nasty browsers like
// NN401, which returns negative decimal numbers for chars>128. I hate it!!
// This handling is unfortunately limited to the user's charset. Anyway, it works
// in most of the cases! Special signs with an unicode>256 return numbers, which
// can not be converted to the actual unicode and so not to the valid utf-8
// representation. Anyway, this function does always return values which can not
// misinterpretd by RC4 or base64 en- or decoding, because every value is >0 and
// <255!!
// Arrays are faster and easier to handle in b64 encoding or encrypting....
function utf8t2d(t)
{
t = t.replace(/\r\n/g,"\n");
var d=new Array; var test=String.fromCharCode(237);
if (test.charCodeAt(0) < 0)
for(var n=0; n<t.length; n++)
{
var c=t.charCodeAt(n);
if (c>0)
d[d.length]= c;
else {
d[d.length]= (((256+c)>>6)|192);
d[d.length]= (((256+c)&63)|128);}
}
else
for(var n=0; n<t.length; n++)
{
var c=t.charCodeAt(n);
// all the signs of asci => 1byte
if (c<128)
d[d.length]= c;
// all the signs between 127 and 2047 => 2byte
else if((c>127) && (c<2048)) {
d[d.length]= ((c>>6)|192);
d[d.length]= ((c&63)|128);}
// all the signs between 2048 and 66536 => 3byte
else {
d[d.length]= ((c>>12)|224);
d[d.length]= (((c>>6)&63)|128);
d[d.length]= ((c&63)|128);}
}
return d;
}
// returns plaintext from an array of bytesrepresenting dezimal numbers, which
// represent an UTF-8 encoded text; browser which does not understand unicode
// like NN401 will show "?"-signs instead
// expects an array of byterepresenting decimals; returns a string
function utf8d2t(d)
{
var r=new Array; var i=0;
while(i<d.length)
{
if (d[i]<128) {
r[r.length]= String.fromCharCode(d[i]); i++;}
else if((d[i]>191) && (d[i]<224)) {
r[r.length]= String.fromCharCode(((d[i]&31)<<6) | (d[i+1]&63)); i+=2;}
else {
r[r.length]= String.fromCharCode(((d[i]&15)<<12) | ((d[i+1]&63)<<6) | (d[i+2]&63)); i+=3;}
}
return r.join("");
}
// included in <body onload="b64arrays"> it creates two arrays which makes base64
// en- and decoding faster
// this speed is noticeable especially when coding larger texts (>5k or so)
function b64arrays() {
var b64s='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
b64 = new Array();f64 =new Array();
for (var i=0; i<b64s.length ;i++) {
b64[i] = b64s.charAt(i);
f64[b64s.charAt(i)] = i;
}
}
// creates a base64 encoded text out of an array of byerepresenting dezimals
// it is really base64 :) this makes serversided handling easier
// expects an array; returns a string
function b64d2t(d) {
var r=new Array; var i=0; var dl=d.length;
// this is for the padding
if ((dl%3) == 1) {
d[d.length] = 0; d[d.length] = 0;}
if ((dl%3) == 2)
d[d.length] = 0;
// from here conversion
while (i<d.length)
{
r[r.length] = b64[d[i]>>2];
r[r.length] = b64[((d[i]&3)<<4) | (d[i+1]>>4)];
r[r.length] = b64[((d[i+1]&15)<<2) | (d[i+2]>>6)];
r[r.length] = b64[d[i+2]&63];
i+=3;
}
// this is again for the padding
if ((dl%3) == 1)
r[r.length-1] = r[r.length-2] = "=";
if ((dl%3) == 2)
r[r.length-1] = "=";
// we join the array to return a textstring
var t=r.join("");
return t;
}
// returns array of byterepresenting numbers created of an base64 encoded text
// it is still the slowest function in this modul; I hope I can make it faster
// expects string; returns an array
function b64t2d(t) {
var d=new Array; var i=0;
// here we fix this CRLF sequenz created by MS-OS; arrrgh!!!
t=t.replace(/\n|\r/g,""); t=t.replace(/=/g,"");
while (i<t.length)
{
d[d.length] = (f64[t.charAt(i)]<<2) | (f64[t.charAt(i+1)]>>4);
d[d.length] = (((f64[t.charAt(i+1)]&15)<<4) | (f64[t.charAt(i+2)]>>2));
d[d.length] = (((f64[t.charAt(i+2)]&3)<<6) | (f64[t.charAt(i+3)]));
i+=4;
}
if (t.length%4 == 2)
d = d.slice(0, d.length-2);
if (t.length%4 == 3)
d = d.slice(0, d.length-1);
return d;
}
if (typeof(atob) == 'undefined' || typeof(btoa) == 'undefined')
b64arrays();
if (typeof(atob) == 'undefined') {
b64decode = function(s) {
return utf8d2t(b64t2d(s));
}
} else {
b64decode = function(s) {
return decodeURIComponent(escape(atob(s)));
}
}
if (typeof(btoa) == 'undefined') {
b64encode = function(s) {
return b64d2t(utf8t2d(s));
}
} else {
b64encode = function(s) {
return btoa(unescape(encodeURIComponent(s)));
}
}
function cnonce(size) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var cnonce = '';
for (var i=0; i<size; i++) {
cnonce += tab.charAt(Math.round(Math.random(new Date().getTime())*(tab.length-1)));
}
return cnonce;
}
JSJAC_HAVEKEYS = true; // whether to use keys
JSJAC_NKEYS = 16; // number of keys to generate
JSJAC_INACTIVITY = 300; // qnd hack to make suspend/resume
// work more smoothly with polling
JSJAC_ERR_COUNT = 10; // number of retries in case of connection
// errors
JSJAC_ALLOW_PLAIN = true; // whether to allow plaintext logins
JSJAC_CHECKQUEUEINTERVAL = 100; // msecs to poll send queue
JSJAC_CHECKINQUEUEINTERVAL = 100; // msecs to poll incoming queue
JSJAC_TIMERVAL = 2000; // default polling interval
// Options specific to HTTP Binding (BOSH)
JSJACHBC_MAX_HOLD = 1; // default for number of connections held by
// connection manager
JSJACHBC_MAX_WAIT = 20; // default 'wait' param - how long an idle connection
// should be held by connection manager
JSJACHBC_BOSH_VERSION = "1.6";
JSJACHBC_USE_BOSH_VER = true;
JSJACHBC_MAXPAUSE = 20; // how long a suspend/resume cycle may take
/*** END CONFIG ***/
/* Copyright (c) 2005-2007 Sam Stephenson
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
json.js
taken from prototype.js, made static
*/
function JSJaCJSON() {}
JSJaCJSON.toString = function (obj) {
var m = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
s = {
array: function (x) {
var a = ['['], b, f, i, l = x.length, v;
for (i = 0; i < l; i += 1) {
v = x[i];
f = s[typeof v];
if (f) {
try {
v = f(v);
if (typeof v == 'string') {
if (b) {
a[a.length] = ',';
}
a[a.length] = v;
b = true;
}
} catch(e) {
}
}
}
a[a.length] = ']';
return a.join('');
},
'boolean': function (x) {
return String(x);
},
'null': function (x) {
return "null";
},
number: function (x) {
return isFinite(x) ? String(x) : 'null';
},
object: function (x) {
if (x) {
if (x instanceof Array) {
return s.array(x);
}
var a = ['{'], b, f, i, v;
for (i in x) {
if (x.hasOwnProperty(i)) {
v = x[i];
f = s[typeof v];
if (f) {
try {
v = f(v);
if (typeof v == 'string') {
if (b) {
a[a.length] = ',';
}
a.push(s.string(i), ':', v);
b = true;
}
} catch(e) {
}
}
}
}
a[a.length] = '}';
return a.join('');
}
return 'null';
},
string: function (x) {
if (/["\\\x00-\x1f]/.test(x)) {
x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
var c = m[b];
if (c) {
return c;
}
c = b.charCodeAt();
return '\\u00' +
Math.floor(c / 16).toString(16) +
(c % 16).toString(16);
});
}
return '"' + x + '"';
}
};
switch (typeof(obj)) {
case 'object':
return s.object(obj);
case 'array':
return s.array(obj);
}
};
JSJaCJSON.parse = function (str) {
try {
return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
str.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + str + ')');
} catch (e) {
return false;
}
};
/**
* @fileoverview This file contains all things that make life easier when
* dealing with JIDs
* @author Stefan Strigler
* @version $Revision$
*/
/**
* list of forbidden chars for nodenames
* @private
*/
var JSJACJID_FORBIDDEN = ['"',' ','&','\'','/',':','<','>','@'];
/**
* Creates a new JSJaCJID object
* @class JSJaCJID models xmpp jid objects
* @constructor
* @param {Object} jid jid may be either of type String or a JID represented
* by JSON with fields 'node', 'domain' and 'resource'
* @throws JSJaCJIDInvalidException Thrown if jid is not valid
* @return a new JSJaCJID object
*/
function JSJaCJID(jid) {
/**
*@private
*/
this._node = '';
/**
*@private
*/
this._domain = '';
/**
*@private
*/
this._resource = '';
if (typeof(jid) == 'string') {
if (jid.indexOf('@') != -1) {
this.setNode(jid.substring(0,jid.indexOf('@')));
jid = jid.substring(jid.indexOf('@')+1);
}
if (jid.indexOf('/') != -1) {
this.setResource(jid.substring(jid.indexOf('/')+1));
jid = jid.substring(0,jid.indexOf('/'));
}
this.setDomain(jid);
} else {
this.setNode(jid.node);
this.setDomain(jid.domain);
this.setResource(jid.resource);
}
}
/**
* Gets the node part of the jid
* @return A string representing the node name
* @type String
*/
JSJaCJID.prototype.getNode = function() { return this._node; };
/**
* Gets the domain part of the jid
* @return A string representing the domain name
* @type String
*/
JSJaCJID.prototype.getDomain = function() { return this._domain; };
/**
* Gets the resource part of the jid
* @return A string representing the resource
* @type String
*/
JSJaCJID.prototype.getResource = function() { return this._resource; };
/**
* Sets the node part of the jid
* @param {String} node Name of the node
* @throws JSJaCJIDInvalidException Thrown if node name contains invalid chars
* @return This object
* @type JSJaCJID
*/
JSJaCJID.prototype.setNode = function(node) {
JSJaCJID._checkNodeName(node);
this._node = node || '';
return this;
};
/**
* Sets the domain part of the jid
* @param {String} domain Name of the domain
* @throws JSJaCJIDInvalidException Thrown if domain name contains invalid
* chars or is empty
* @return This object
* @type JSJaCJID
*/
JSJaCJID.prototype.setDomain = function(domain) {
if (!domain || domain == '')
throw new JSJaCJIDInvalidException("domain name missing");
// chars forbidden for a node are not allowed in domain names
// anyway, so let's check
JSJaCJID._checkNodeName(domain);
this._domain = domain;
return this;
};
/**
* Sets the resource part of the jid
* @param {String} resource Name of the resource
* @return This object
* @type JSJaCJID
*/
JSJaCJID.prototype.setResource = function(resource) {
this._resource = resource || '';
return this;
};
/**
* The string representation of the full jid
* @return A string representing the jid
* @type String
*/
JSJaCJID.prototype.toString = function() {
var jid = '';
if (this.getNode() && this.getNode() != '')
jid = this.getNode() + '@';
jid += this.getDomain(); // we always have a domain
if (this.getResource() && this.getResource() != "")
jid += '/' + this.getResource();
return jid;
};
/**
* Removes the resource part of the jid
* @return This object
* @type JSJaCJID
*/
JSJaCJID.prototype.removeResource = function() {
return this.setResource();
};
/**
* creates a copy of this JSJaCJID object
* @return A copy of this
* @type JSJaCJID
*/
JSJaCJID.prototype.clone = function() {
return new JSJaCJID(this.toString());
};
/**
* Compares two jids if they belong to the same entity (i.e. w/o resource)
* @param {String} jid a jid as string or JSJaCJID object
* @return 'true' if jid is same entity as this
* @type Boolean
*/
JSJaCJID.prototype.isEntity = function(jid) {
if (typeof jid == 'string')
jid = (new JSJaCJID(jid));
jid.removeResource();
return (this.clone().removeResource().toString() === jid.toString());
};
/**
* Check if node name is valid
* @private
* @param {String} node A name for a node
* @throws JSJaCJIDInvalidException Thrown if name for node is not allowed
*/
JSJaCJID._checkNodeName = function(nodeprep) {
if (!nodeprep || nodeprep == '')
return;
for (var i=0; i< JSJACJID_FORBIDDEN.length; i++) {
if (nodeprep.indexOf(JSJACJID_FORBIDDEN[i]) != -1) {
throw new JSJaCJIDInvalidException("forbidden char in nodename: "+JSJACJID_FORBIDDEN[i]);
}
}
};
/**
* Creates a new Exception of type JSJaCJIDInvalidException
* @class Exception to indicate invalid values for a jid
* @constructor
* @param {String} message The message associated with this Exception
*/
function JSJaCJIDInvalidException(message) {
/**
* The exceptions associated message
* @type String
*/
this.message = message;
/**
* The name of the exception
* @type String
*/
this.name = "JSJaCJIDInvalidException";
}
/* Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @private
* This code is taken from {@link
* http://wiki.script.aculo.us/scriptaculous/show/Builder
* script.aculo.us' Dom Builder} and has been modified to suit our
* needs.<br/>
* The original parts of the code do have the following
* copyright and license notice:<br/>
* Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us,
* http://mir.acu lo.us) <br/>
* script.aculo.us is freely distributable under the terms of an
* MIT-style license.<br>
* For details, see the script.aculo.us web site:
* http://script.aculo.us/<br>
*/
var JSJaCBuilder = {
/**
* @private
*/
buildNode: function(doc, elementName) {
var element, ns = arguments[4];
// attributes (or text)
if(arguments[2])
if(JSJaCBuilder._isStringOrNumber(arguments[2]) ||
(arguments[2] instanceof Array)) {
element = this._createElement(doc, elementName, ns);
JSJaCBuilder._children(doc, element, arguments[2]);
} else {
ns = arguments[2]['xmlns'] || ns;
element = this._createElement(doc, elementName, ns);
for(attr in arguments[2]) {
if (arguments[2].hasOwnProperty(attr) && attr != 'xmlns')
element.setAttribute(attr, arguments[2][attr]);
}
}
else
element = this._createElement(doc, elementName, ns);
// text, or array of children
if(arguments[3])
JSJaCBuilder._children(doc, element, arguments[3], ns);
return element;
},
_createElement: function(doc, elementName, ns) {
try {
if (ns)
return doc.createElementNS(ns, elementName);
} catch (ex) { }
var el = doc.createElement(elementName);
if (ns)
el.setAttribute("xmlns", ns);
return el;
},
/**
* @private
*/
_text: function(doc, text) {
return doc.createTextNode(text);
},
/**
* @private
*/
_children: function(doc, element, children, ns) {
if(typeof children=='object') { // array can hold nodes and text
for (var i in children) {
if (children.hasOwnProperty(i)) {
var e = children[i];
if (typeof e=='object') {
if (e instanceof Array) {
var node = JSJaCBuilder.buildNode(doc, e[0], e[1], e[2], ns);
element.appendChild(node);
} else {
element.appendChild(e);
}
} else {
if(JSJaCBuilder._isStringOrNumber(e)) {
element.appendChild(JSJaCBuilder._text(doc, e));
}
}
}
}
} else {
if(JSJaCBuilder._isStringOrNumber(children)) {
element.appendChild(JSJaCBuilder._text(doc, children));
}
}
},
_attributes: function(attributes) {
var attrs = [];
for(attribute in attributes)
if (attributes.hasOwnProperty(attribute))
attrs.push(attribute +
'="' + attributes[attribute].toString().htmlEnc() + '"');
return attrs.join(" ");
},
_isStringOrNumber: function(param) {
return(typeof param=='string' || typeof param=='number');
}
};
/**
* @fileoverview Contains all Jabber/XMPP packet related classes.
* @author Stefan Strigler steve@zeank.in-berlin.de
* @version $Revision$
*/
var JSJACPACKET_USE_XMLNS = true;
/**
* Creates a new packet with given root tag name (for internal use)
* @class Somewhat abstract base class for all kinds of specialised packets
* @param {String} name The root tag name of the packet
* (i.e. one of 'message', 'iq' or 'presence')
*/
function JSJaCPacket(name) {
/**
* @private
*/
this.name = name;
if (typeof(JSJACPACKET_USE_XMLNS) != 'undefined' && JSJACPACKET_USE_XMLNS)
/**
* @private
*/
this.doc = XmlDocument.create(name,'jabber:client');
else
/**
* @private
*/
this.doc = XmlDocument.create(name,'');
}
/**
* Gets the type (name of root element) of this packet, i.e. one of
* 'presence', 'message' or 'iq'
* @return the top level tag name
* @type String
*/
JSJaCPacket.prototype.pType = function() { return this.name; };
/**
* Gets the associated Document for this packet.
* @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document Document}
*/
JSJaCPacket.prototype.getDoc = function() {
return this.doc;
};
/**
* Gets the root node of this packet
* @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
*/
JSJaCPacket.prototype.getNode = function() {
if (this.getDoc() && this.getDoc().documentElement)
return this.getDoc().documentElement;
else
return null;
};
/**
* Sets the 'to' attribute of the root node of this packet
* @param {String} to
* @type JSJaCPacket
*/
JSJaCPacket.prototype.setTo = function(to) {
if (!to || to == '')
this.getNode().removeAttribute('to');
else if (typeof(to) == 'string')
this.getNode().setAttribute('to',to);
else
this.getNode().setAttribute('to',to.toString());
return this;
};
/**
* Sets the 'from' attribute of the root node of this
* packet. Usually this is not needed as the server will take care
* of this automatically.
* @type JSJaCPacket
*/
JSJaCPacket.prototype.setFrom = function(from) {
if (!from || from == '')
this.getNode().removeAttribute('from');
else if (typeof(from) == 'string')
this.getNode().setAttribute('from',from);
else
this.getNode().setAttribute('from',from.toString());
return this;
};
/**
* Sets 'id' attribute of the root node of this packet.
* @param {String} id The id of the packet.
* @type JSJaCPacket
*/
JSJaCPacket.prototype.setID = function(id) {
if (!id || id == '')
this.getNode().removeAttribute('id');
else
this.getNode().setAttribute('id',id);
return this;
};
/**
* Sets the 'type' attribute of the root node of this packet.
* @param {String} type The type of the packet.
* @type JSJaCPacket
*/
JSJaCPacket.prototype.setType = function(type) {
if (!type || type == '')
this.getNode().removeAttribute('type');
else
this.getNode().setAttribute('type',type);
return this;
};
/**
* Sets 'xml:lang' for this packet
* @param {String} xmllang The xml:lang of the packet.
* @type JSJaCPacket
*/
JSJaCPacket.prototype.setXMLLang = function(xmllang) {
// Fix IE bug with xml:lang attribute
// Also due to issues with both BD and jQuery being used, employ a simple regexp since the detection
// here is very limited.
if (navigator.appVersion.match(/^.*MSIE (\d)/))
return this;
if (!xmllang || xmllang == '')
this.getNode().removeAttribute('xml:lang');
else
this.getNode().setAttribute('xml:lang',xmllang);
return this;
};
/**
* Gets the 'to' attribute of this packet
* @type String
*/
JSJaCPacket.prototype.getTo = function() {
return this.getNode().getAttribute('to');
};
/**
* Gets the 'from' attribute of this packet.
* @type String
*/
JSJaCPacket.prototype.getFrom = function() {
return this.getNode().getAttribute('from');
};
/**
* Gets the 'to' attribute of this packet as a JSJaCJID object
* @type JSJaCJID
*/
JSJaCPacket.prototype.getToJID = function() {
return new JSJaCJID(this.getTo());
};
/**
* Gets the 'from' attribute of this packet as a JSJaCJID object
* @type JSJaCJID
*/
JSJaCPacket.prototype.getFromJID = function() {
return new JSJaCJID(this.getFrom());
};
/**
* Gets the 'id' of this packet
* @type String
*/
JSJaCPacket.prototype.getID = function() {
return this.getNode().getAttribute('id');
};
/**
* Gets the 'type' of this packet
* @type String
*/
JSJaCPacket.prototype.getType = function() {
return this.getNode().getAttribute('type');
};
/**
* Gets the 'xml:lang' of this packet
* @type String
*/
JSJaCPacket.prototype.getXMLLang = function() {
return this.getNode().getAttribute('xml:lang');
};
/**
* Gets the 'xmlns' (xml namespace) of the root node of this packet
* @type String
*/
JSJaCPacket.prototype.getXMLNS = function() {
return this.getNode().namespaceURI || this.getNode().getAttribute('xmlns');
};
/**
* Gets a child element of this packet. If no params given returns first child.
* @param {String} name Tagname of child to retrieve. Use '*' to match any tag. [optional]
* @param {String} ns Namespace of child. Use '*' to match any ns.[optional]
* @return The child node, null if none found
* @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
*/
JSJaCPacket.prototype.getChild = function(name, ns) {
if (!this.getNode()) {
return null;
}
name = name || '*';
ns = ns || '*';
if (this.getNode().getElementsByTagNameNS) {
return this.getNode().getElementsByTagNameNS(ns, name).item(0);
}
// fallback
var nodes = this.getNode().getElementsByTagName(name);
if (ns != '*') {
for (var i=0; i<nodes.length; i++) {
if (nodes.item(i).namespaceURI == ns || nodes.item(i).getAttribute('xmlns') == ns) {
return nodes.item(i);
}
}
} else {
return nodes.item(0);
}
return null; // nothing found
};
/**
* Gets the node value of a child element of this packet.
* @param {String} name Tagname of child to retrieve.
* @param {String} ns Namespace of child
* @return The value of the child node, empty string if none found
* @type String
*/
JSJaCPacket.prototype.getChildVal = function(name, ns) {
var node = this.getChild(name, ns);
var ret = '';
if (node && node.hasChildNodes()) {
// concatenate all values from childNodes
for (var i=0; i<node.childNodes.length; i++)
if (node.childNodes.item(i).nodeValue)
ret += node.childNodes.item(i).nodeValue;
}
return ret;
};
/**
* Returns a copy of this node
* @return a copy of this node
* @type JSJaCPacket
*/
JSJaCPacket.prototype.clone = function() {
return JSJaCPacket.wrapNode(this.getNode());
};
/**
* Checks if packet is of type 'error'
* @return 'true' if this packet is of type 'error', 'false' otherwise
* @type boolean
*/
JSJaCPacket.prototype.isError = function() {
return (this.getType() == 'error');
};
/**
* Returns an error condition reply according to {@link http://www.xmpp.org/extensions/xep-0086.html XEP-0086}. Creates a clone of the calling packet with senders and recipient exchanged and error stanza appended.
* @param {STANZA_ERROR} stanza_error an error stanza containing error cody, type and condition of the error to be indicated
* @return an error reply packet
* @type JSJaCPacket
*/
JSJaCPacket.prototype.errorReply = function(stanza_error) {
var rPacket = this.clone();
rPacket.setTo(this.getFrom());
rPacket.setFrom();
rPacket.setType('error');
rPacket.appendNode('error',
{code: stanza_error.code, type: stanza_error.type},
[[stanza_error.cond]]);
return rPacket;
};
/**
* Returns a string representation of the raw xml content of this packet.
* @type String
*/
JSJaCPacket.prototype.xml = typeof XMLSerializer != 'undefined' ?
function() {
var r = (new XMLSerializer()).serializeToString(this.getNode());
if (typeof(r) == 'undefined')
r = (new XMLSerializer()).serializeToString(this.doc); // oldschool
return r
} :
function() {// IE
return this.getDoc().xml
};
// PRIVATE METHODS DOWN HERE
/**
* Gets an attribute of the root element
* @private
*/
JSJaCPacket.prototype._getAttribute = function(attr) {
return this.getNode().getAttribute(attr);
};
if (document.ELEMENT_NODE == null) {
document.ELEMENT_NODE = 1;
document.ATTRIBUTE_NODE = 2;
document.TEXT_NODE = 3;
document.CDATA_SECTION_NODE = 4;
document.ENTITY_REFERENCE_NODE = 5;
document.ENTITY_NODE = 6;
document.PROCESSING_INSTRUCTION_NODE = 7;
document.COMMENT_NODE = 8;
document.DOCUMENT_NODE = 9;
document.DOCUMENT_TYPE_NODE = 10;
document.DOCUMENT_FRAGMENT_NODE = 11;
document.NOTATION_NODE = 12;
}
/**
* import node into this packets document
* @private
*/
JSJaCPacket.prototype._importNode = function(node, allChildren) {
switch (node.nodeType) {
case document.ELEMENT_NODE:
if (this.getDoc().createElementNS) {
var newNode = this.getDoc().createElementNS(node.namespaceURI, node.nodeName);
} else {
var newNode = this.getDoc().createElement(node.nodeName);
}
/* does the node have any attributes to add? */
if (node.attributes && node.attributes.length > 0)
for (var i = 0, il = node.attributes.length;i < il; i++) {
var attr = node.attributes.item(i);
if (attr.nodeName == 'xmlns' && newNode.getAttribute('xmlns') != null ) continue;
if (newNode.setAttributeNS && attr.namespaceURI) {
newNode.setAttributeNS(attr.namespaceURI,
attr.nodeName,
attr.nodeValue);
} else {
newNode.setAttribute(attr.nodeName,
attr.nodeValue);
}
}
/* are we going after children too, and does the node have any? */
if (allChildren && node.childNodes && node.childNodes.length > 0) {
for (var i = 0, il = node.childNodes.length; i < il; i++) {
newNode.appendChild(this._importNode(node.childNodes.item(i), allChildren));
}
}
return newNode;
break;
case document.TEXT_NODE:
case document.CDATA_SECTION_NODE:
case document.COMMENT_NODE:
return this.getDoc().createTextNode(node.nodeValue);
break;
}
};
/**
* Set node value of a child node
* @private
*/
JSJaCPacket.prototype._setChildNode = function(nodeName, nodeValue) {
var aNode = this.getChild(nodeName);
var tNode = this.getDoc().createTextNode(nodeValue);
if (aNode)
try {
aNode.replaceChild(tNode,aNode.firstChild);
} catch (e) { }
else {
try {
aNode = this.getDoc().createElementNS(this.getNode().namespaceURI,
nodeName);
} catch (ex) {
aNode = this.getDoc().createElement(nodeName)
}
this.getNode().appendChild(aNode);
aNode.appendChild(tNode);
}
return aNode;
};
/**
* Builds a node using {@link
* http://wiki.script.aculo.us/scriptaculous/show/Builder
* script.aculo.us' Dom Builder} notation.
* This code is taken from {@link
* http://wiki.script.aculo.us/scriptaculous/show/Builder
* script.aculo.us' Dom Builder} and has been modified to suit our
* needs.<br/>
* The original parts of the code do have the following copyright
* and license notice:<br/>
* Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us,
* http://mir.acu lo.us) <br/>
* script.aculo.us is freely distributable under the terms of an
* MIT-style licen se. // For details, see the script.aculo.us web
* site: http://script.aculo.us/<br>
* @author Thomas Fuchs
* @author Stefan Strigler
* @return The newly created node
* @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
*/
JSJaCPacket.prototype.buildNode = function(elementName) {
return JSJaCBuilder.buildNode(this.getDoc(),
elementName,
arguments[1],
arguments[2]);
};
/**
* Appends node created by buildNode to this packets parent node.
* @param {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node} element The node to append or
* @param {String} element A name plus an object hash with attributes (optional) plus an array of childnodes (optional)
* @see #buildNode
* @return This packet
* @type JSJaCPacket
*/
JSJaCPacket.prototype.appendNode = function(element) {
if (typeof element=='object') { // seems to be a prebuilt node
return this.getNode().appendChild(element)
} else { // build node
return this.getNode().appendChild(this.buildNode(element,
arguments[1],
arguments[2],
null,
this.getNode().namespaceURI));
}
};
/**
* A jabber/XMPP presence packet
* @class Models the XMPP notion of a 'presence' packet
* @extends JSJaCPacket
*/
function JSJaCPresence() {
/**
* @ignore
*/
this.base = JSJaCPacket;
this.base('presence');
}
JSJaCPresence.prototype = new JSJaCPacket;
/**
* Sets the status message for current status. Usually this is set
* to some human readable string indicating what the user is
* doing/feel like currently.
* @param {String} status A status message
* @return this
* @type JSJaCPacket
*/
JSJaCPresence.prototype.setStatus = function(status) {
this._setChildNode("status", status);
return this;
};
/**
* Sets the online status for this presence packet.
* @param {String} show An XMPP complient status indicator. Must
* be one of 'chat', 'away', 'xa', 'dnd'
* @return this
* @type JSJaCPacket
*/
JSJaCPresence.prototype.setShow = function(show) {
if (show == 'chat' || show == 'away' || show == 'xa' || show == 'dnd')
this._setChildNode("show",show);
return this;
};
/**
* Sets the priority of the resource bind to with this connection
* @param {int} prio The priority to set this resource to
* @return this
* @type JSJaCPacket
*/
JSJaCPresence.prototype.setPriority = function(prio) {
this._setChildNode("priority", prio);
return this;
};
/**
* Some combined method that allowes for setting show, status and
* priority at once
* @param {String} show A status message
* @param {String} status A status indicator as defined by XMPP
* @param {int} prio A priority for this resource
* @return this
* @type JSJaCPacket
*/
JSJaCPresence.prototype.setPresence = function(show,status,prio) {
if (show)
this.setShow(show);
if (status)
this.setStatus(status);
if (prio)
this.setPriority(prio);
return this;
};
/**
* Gets the status message of this presence
* @return The (human readable) status message
* @type String
*/
JSJaCPresence.prototype.getStatus = function() {
return this.getChildVal('status');
};
/**
* Gets the status of this presence.
* Either one of 'chat', 'away', 'xa' or 'dnd' or null.
* @return The status indicator as defined by XMPP
* @type String
*/
JSJaCPresence.prototype.getShow = function() {
return this.getChildVal('show');
};
/**
* Gets the priority of this status message
* @return A resource priority
* @type int
*/
JSJaCPresence.prototype.getPriority = function() {
return this.getChildVal('priority');
};
/**
* A jabber/XMPP iq packet
* @class Models the XMPP notion of an 'iq' packet
* @extends JSJaCPacket
*/
function JSJaCIQ() {
/**
* @ignore
*/
this.base = JSJaCPacket;
this.base('iq');
}
JSJaCIQ.prototype = new JSJaCPacket;
/**
* Some combined method to set 'to', 'type' and 'id' at once
* @param {String} to the recepients JID
* @param {String} type A XMPP compliant iq type (one of 'set', 'get', 'result' and 'error'
* @param {String} id A packet ID
* @return this
* @type JSJaCIQ
*/
JSJaCIQ.prototype.setIQ = function(to,type,id) {
if (to)
this.setTo(to);
if (type)
this.setType(type);
if (id)
this.setID(id);
return this;
};
/**
* Creates a 'query' child node with given XMLNS
* @param {String} xmlns The namespace for the 'query' node
* @return The query node
* @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
*/
JSJaCIQ.prototype.setQuery = function(xmlns) {
var query;
try {
query = this.getDoc().createElementNS(xmlns,'query');
} catch (e) {
query = this.getDoc().createElement('query');
query.setAttribute('xmlns',xmlns);
}
this.getNode().appendChild(query);
return query;
};
/**
* Gets the 'query' node of this packet
* @return The query node
* @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
*/
JSJaCIQ.prototype.getQuery = function() {
return this.getNode().getElementsByTagName('query').item(0);
};
/**
* Gets the XMLNS of the query node contained within this packet
* @return The namespace of the query node
* @type String
*/
JSJaCIQ.prototype.getQueryXMLNS = function() {
if (this.getQuery()) {
return this.getQuery().namespaceURI || this.getQuery().getAttribute('xmlns');
} else {
return null;
}
};
/**
* Creates an IQ reply with type set to 'result'. If given appends payload to first child if IQ. Payload maybe XML as string or a DOM element (or an array of such elements as well).
* @param {Element} payload A payload to be appended [optional]
* @return An IQ reply packet
* @type JSJaCIQ
*/
JSJaCIQ.prototype.reply = function(payload) {
var rIQ = this.clone();
rIQ.setTo(this.getFrom());
rIQ.setFrom();
rIQ.setType('result');
if (payload) {
if (typeof payload == 'string')
rIQ.getChild().appendChild(rIQ.getDoc().loadXML(payload));
else if (payload.constructor == Array) {
var node = rIQ.getChild();
for (var i=0; i<payload.length; i++)
if(typeof payload[i] == 'string')
node.appendChild(rIQ.getDoc().loadXML(payload[i]));
else if (typeof payload[i] == 'object')
node.appendChild(payload[i]);
}
else if (typeof payload == 'object')
rIQ.getChild().appendChild(payload);
}
return rIQ;
};
/**
* A jabber/XMPP message packet
* @class Models the XMPP notion of an 'message' packet
* @extends JSJaCPacket
*/
function JSJaCMessage() {
/**
* @ignore
*/
this.base = JSJaCPacket;
this.base('message');
}
JSJaCMessage.prototype = new JSJaCPacket;
/**
* Sets the body of the message
* @param {String} body Your message to be sent along
* @return this message
* @type JSJaCMessage
*/
JSJaCMessage.prototype.setBody = function(body) {
this._setChildNode("body",body);
return this;
};
/**
* Sets the subject of the message
* @param {String} subject Your subject to be sent along
* @return this message
* @type JSJaCMessage
*/
JSJaCMessage.prototype.setSubject = function(subject) {
this._setChildNode("subject",subject);
return this;
};
/**
* Sets the 'tread' attribute for this message. This is used to identify
* threads in chat conversations
* @param {String} thread Usually a somewhat random hash.
* @return this message
* @type JSJaCMessage
*/
JSJaCMessage.prototype.setThread = function(thread) {
this._setChildNode("thread", thread);
return this;
};
/**
* Sets the 'nick' attribute for this message.
* This is sometime sused to detect the sender nickname when he's not in the roster
* @param {String} nickname
* @return this message
* @type JSJaCMessage
*/
JSJaCMessage.prototype.setNick = function(nick) {
var aNode = this.getChild("nick");
var tNode = this.getDoc().createTextNode(nick);
if (aNode)
try {
aNode.replaceChild(tNode,aNode.firstChild);
} catch (e) { }
else {
try {
aNode = this.getDoc().createElementNS('http://jabber.org/protocol/nick',
"nick");
} catch (ex) {
aNode = this.getDoc().createElement("nick")
}
this.getNode().appendChild(aNode);
aNode.appendChild(tNode);
}
return this;
};
/**
* Gets the 'thread' identifier for this message
* @return A thread identifier
* @type String
*/
JSJaCMessage.prototype.getThread = function() {
return this.getChildVal('thread');
};
/**
* Gets the body of this message
* @return The body of this message
* @type String
*/
JSJaCMessage.prototype.getBody = function() {
return this.getChildVal('body');
};
/**
* Gets the subject of this message
* @return The subject of this message
* @type String
*/
JSJaCMessage.prototype.getSubject = function() {
return this.getChildVal('subject')
};
/**
* Gets the nickname of this message
* @return The nickname of this message
* @type String
*/
JSJaCMessage.prototype.getNick = function() {
return this.getChildVal('nick');
};
/**
* Tries to transform a w3c DOM node to JSJaC's internal representation
* (JSJaCPacket type, one of JSJaCPresence, JSJaCMessage, JSJaCIQ)
* @param: {Node
* http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247}
* node The node to be transformed
* @return A JSJaCPacket representing the given node. If node's root
* elemenent is not one of 'message', 'presence' or 'iq',
* <code>null</code> is being returned.
* @type JSJaCPacket
*/
JSJaCPacket.wrapNode = function(node) {
var oPacket = null;
switch (node.nodeName.toLowerCase()) {
case 'presence':
oPacket = new JSJaCPresence();
break;
case 'message':
oPacket = new JSJaCMessage();
break;
case 'iq':
oPacket = new JSJaCIQ();
break;
}
if (oPacket) {
oPacket.getDoc().replaceChild(oPacket._importNode(node, true),
oPacket.getNode());
}
return oPacket;
};
/**
* an error packet for internal use
* @private
* @constructor
*/
function JSJaCError(code,type,condition) {
var xmldoc = XmlDocument.create("error","jsjac");
xmldoc.documentElement.setAttribute('code',code);
xmldoc.documentElement.setAttribute('type',type);
if (condition)
xmldoc.documentElement.appendChild(xmldoc.createElement(condition)).
setAttribute('xmlns','urn:ietf:params:xml:ns:xmpp-stanzas');
return xmldoc.documentElement;
}
/**
* Creates a new set of hash keys
* @class Reflects a set of sha1/md5 hash keys for securing sessions
* @constructor
* @param {Function} func The hash function to be used for creating the keys
* @param {Debugger} oDbg Reference to debugger implementation [optional]
*/
function JSJaCKeys(func,oDbg) {
var seed = Math.random();
/**
* @private
*/
this._k = new Array();
this._k[0] = seed.toString();
if (oDbg)
/**
* Reference to Debugger
* @type Debugger
*/
this.oDbg = oDbg;
else {
this.oDbg = {};
this.oDbg.log = function() {};
}
if (func) {
for (var i=1; i<JSJAC_NKEYS; i++) {
this._k[i] = func(this._k[i-1]);
oDbg.log(i+": "+this._k[i],4);
}
}
/**
* @private
*/
this._indexAt = JSJAC_NKEYS-1;
/**
* Gets next key from stack
* @return New hash key
* @type String
*/
this.getKey = function() {
return this._k[this._indexAt--];
};
/**
* Indicates whether there's only one key left
* @return <code>true</code> if there's only one key left, false otherwise
* @type boolean
*/
this.lastKey = function() { return (this._indexAt == 0); };
/**
* Returns number of overall/initial stack size
* @return Number of keys created
* @type int
*/
this.size = function() { return this._k.length; };
/**
* @private
*/
this._getSuspendVars = function() {
return ('_k,_indexAt').split(',');
}
}
/**
* @fileoverview Contains all things in common for all subtypes of connections
* supported.
* @author Stefan Strigler steve@zeank.in-berlin.de
* @version $Revision$
*/
/**
* Creates a new Jabber connection (a connection to a jabber server)
* @class Somewhat abstract base class for jabber connections. Contains all
* of the code in common for all jabber connections
* @constructor
* @param {JSON http://www.json.org/index} oArg JSON with properties: <br>
* * <code>httpbase</code> the http base address of the service to be used for
* connecting to jabber<br>
* * <code>oDbg</code> (optional) a reference to a debugger interface
*/
function JSJaCConnection(oArg) {
if (oArg && oArg.oDbg && oArg.oDbg.log) {
/**
* Reference to debugger interface
* (needs to implement method <code>log</code>)
* @type Debugger
*/
this.oDbg = oArg.oDbg;
} else {
this.oDbg = new Object(); // always initialise a debugger
this.oDbg.log = function() { };
}
if (oArg && oArg.timerval)
this.setPollInterval(oArg.timerval);
else
this.setPollInterval(JSJAC_TIMERVAL);
if (oArg && oArg.httpbase)
/**
* @private
*/
this._httpbase = oArg.httpbase;
if (oArg &&oArg.allow_plain)
/**
* @private
*/
this.allow_plain = oArg.allow_plain;
else
this.allow_plain = JSJAC_ALLOW_PLAIN;
if (oArg && oArg.cookie_prefix)
/**
* @private
*/
this._cookie_prefix = oArg.cookie_prefix;
else
this._cookie_prefix = "";
/**
* @private
*/
this._connected = false;
/**
* @private
*/
this._events = new Array();
/**
* @private
*/
this._keys = null;
/**
* @private
*/
this._ID = 0;
/**
* @private
*/
this._inQ = new Array();
/**
* @private
*/
this._pQueue = new Array();
/**
* @private
*/
this._regIDs = new Array();
/**
* @private
*/
this._req = new Array();
/**
* @private
*/
this._status = 'intialized';
/**
* @private
*/
this._errcnt = 0;
/**
* @private
*/
this._inactivity = JSJAC_INACTIVITY;
/**
* @private
*/
this._sendRawCallbacks = new Array();
}
// Generates an ID
var STANZA_ID = 1;
function genID() {
return STANZA_ID++;
}
JSJaCConnection.prototype.connect = function(oArg) {
this._setStatus('connecting');
this.domain = oArg.domain || 'localhost';
this.username = oArg.username;
this.resource = oArg.resource;
this.pass = oArg.pass;
this.register = oArg.register;
this.authhost = oArg.authhost || this.domain;
this.authtype = oArg.authtype || 'sasl';
if (oArg.xmllang && oArg.xmllang != '')
this._xmllang = oArg.xmllang;
else
this._xmllang = 'en';
this.host = oArg.host || this.domain;
this.port = oArg.port || 5222;
if (oArg.secure)
this.secure = 'true';
else
this.secure = 'false';
if (oArg.wait)
this._wait = oArg.wait;
this.jid = this.username + '@' + this.domain;
this.fulljid = this.jid + '/' + this.resource;
this._rid = Math.round( 100000.5 + ( ( (900000.49999) - (100000.5) ) * Math.random() ) );
// setupRequest must be done after rid is created but before first use in reqstr
var slot = this._getFreeSlot();
this._req[slot] = this._setupRequest(true);
var reqstr = this._getInitialRequestString();
this.oDbg.log(reqstr,4);
this._req[slot].r.onreadystatechange =
JSJaC.bind(function() {
var r = this._req[slot].r;
if (r.readyState == 4) {
this.oDbg.log("async recv: "+r.responseText,4);
this._handleInitialResponse(r); // handle response
}
}, this);
if (typeof(this._req[slot].r.onerror) != 'undefined') {
this._req[slot].r.onerror =
JSJaC.bind(function(e) {
this.oDbg.log('XmlHttpRequest error',1);
return false;
}, this);
}
this._req[slot].r.send(reqstr);
};
/**
* Tells whether this connection is connected
* @return <code>true</code> if this connections is connected,
* <code>false</code> otherwise
* @type boolean
*/
JSJaCConnection.prototype.connected = function() { return this._connected; };
/**
* Disconnects from jabber server and terminates session (if applicable)
*/
JSJaCConnection.prototype.disconnect = function() {
this._setStatus('disconnecting');
if (!this.connected())
return;
this._connected = false;
clearInterval(this._interval);
clearInterval(this._inQto);
if (this._timeout)
clearTimeout(this._timeout); // remove timer
var slot = this._getFreeSlot();
// Intentionally synchronous
this._req[slot] = this._setupRequest(false);
request = this._getRequestString(false, true);
this.oDbg.log("Disconnecting: " + request,4);
this._req[slot].r.send(request);
try {
removeDB('jsjac', 'state');
} catch (e) {}
this.oDbg.log("Disconnected: "+this._req[slot].r.responseText,2);
this._handleEvent('ondisconnect');
};
/**
* Gets current value of polling interval
* @return Polling interval in milliseconds
* @type int
*/
JSJaCConnection.prototype.getPollInterval = function() {
return this._timerval;
};
/**
* Registers an event handler (callback) for this connection.
* <p>Note: All of the packet handlers for specific packets (like
* message_in, presence_in and iq_in) fire only if there's no
* callback associated with the id.<br>
* <p>Example:<br/>
* <code>con.registerHandler('iq', 'query', 'jabber:iq:version', handleIqVersion);</code>
* @param {String} event One of
* <ul>
* <li>onConnect - connection has been established and authenticated</li>
* <li>onDisconnect - connection has been disconnected</li>
* <li>onResume - connection has been resumed</li>
* <li>onStatusChanged - connection status has changed, current
* status as being passed argument to handler. See {@link #status}.</li>
* <li>onError - an error has occured, error node is supplied as
* argument, like this:<br><code>&lt;error code='404' type='cancel'&gt;<br>
* &lt;item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/&gt;<br>
* &lt;/error&gt;</code></li>
* <li>packet_in - a packet has been received (argument: the
* packet)</li>
* <li>packet_out - a packet is to be sent(argument: the
* packet)</li>
* <li>message_in | message - a message has been received (argument:
* the packet)</li>
* <li>message_out - a message packet is to be sent (argument: the
* packet)</li>
* <li>presence_in | presence - a presence has been received
* (argument: the packet)</li>
* <li>presence_out - a presence packet is to be sent (argument: the
* packet)</li>
* <li>iq_in | iq - an iq has been received (argument: the packet)</li>
* <li>iq_out - an iq is to be sent (argument: the packet)</li>
* </ul>
* @param {String} childName A childnode's name that must occur within a
* retrieved packet [optional]
* @param {String} childNS A childnode's namespace that must occure within
* a retrieved packet (works only if childName is given) [optional]
* @param {String} type The type of the packet to handle (works only if childName and chidNS are given (both may be set to '*' in order to get skipped) [optional]
* @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
*/
JSJaCConnection.prototype.registerHandler = function(event) {
event = event.toLowerCase(); // don't be case-sensitive here
var eArg = {handler: arguments[arguments.length-1],
childName: '*',
childNS: '*',
type: '*'};
if (arguments.length > 2)
eArg.childName = arguments[1];
if (arguments.length > 3)
eArg.childNS = arguments[2];
if (arguments.length > 4)
eArg.type = arguments[3];
if (!this._events[event])
this._events[event] = new Array(eArg);
else
this._events[event] = this._events[event].concat(eArg);
// sort events in order how specific they match criterias thus using
// wildcard patterns puts them back in queue when it comes to
// bubbling the event
this._events[event] =
this._events[event].sort(function(a,b) {
var aRank = 0;
var bRank = 0;
with (a) {
if (type == '*')
aRank++;
if (childNS == '*')
aRank++;
if (childName == '*')
aRank++;
}
with (b) {
if (type == '*')
bRank++;
if (childNS == '*')
bRank++;
if (childName == '*')
bRank++;
}
if (aRank > bRank)
return 1;
if (aRank < bRank)
return -1;
return 0;
});
this.oDbg.log("registered handler for event '"+event+"'",2);
};
JSJaCConnection.prototype.unregisterHandler = function(event,handler) {
event = event.toLowerCase(); // don't be case-sensitive here
if (!this._events[event])
return;
var arr = this._events[event], res = new Array();
for (var i=0; i<arr.length; i++)
if (arr[i].handler != handler)
res.push(arr[i]);
if (arr.length != res.length) {
this._events[event] = res;
this.oDbg.log("unregistered handler for event '"+event+"'",2);
}
};
/**
* Register for iq packets of type 'get'.
* @param {String} childName A childnode's name that must occur within a
* retrieved packet
* @param {String} childNS A childnode's namespace that must occure within
* a retrieved packet (works only if childName is given)
* @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
*/
JSJaCConnection.prototype.registerIQGet = function(childName, childNS, handler) {
this.registerHandler('iq', childName, childNS, 'get', handler);
};
/**
* Register for iq packets of type 'set'.
* @param {String} childName A childnode's name that must occur within a
* retrieved packet
* @param {String} childNS A childnode's namespace that must occure within
* a retrieved packet (works only if childName is given)
* @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
*/
JSJaCConnection.prototype.registerIQSet = function(childName, childNS, handler) {
this.registerHandler('iq', childName, childNS, 'set', handler);
};
/**
* Resumes this connection from saved state (cookie)
* @return Whether resume was successful
* @type boolean
*/
JSJaCConnection.prototype.resume = function() {
try {
var json = getDB('jsjac', 'state');
this.oDbg.log('read cookie: '+json,2);
removeDB('jsjac', 'state');
return this.resumeFromData(JSJaCJSON.parse(json));
} catch (e) {}
return false; // sth went wrong
};
/**
* Resumes BOSH connection from data
* @param {Object} serialized jsjac state information
* @return Whether resume was successful
* @type boolean
*/
JSJaCConnection.prototype.resumeFromData = function(data) {
try {
this._setStatus('resuming');
for (var i in data)
if (data.hasOwnProperty(i))
this[i] = data[i];
// copy keys - not being very generic here :-/
if (this._keys) {
this._keys2 = new JSJaCKeys();
var u = this._keys2._getSuspendVars();
for (var i=0; i<u.length; i++)
this._keys2[u[i]] = this._keys[u[i]];
this._keys = this._keys2;
}
if (this._connected) {
// don't poll too fast!
this._handleEvent('onresume');
setTimeout(JSJaC.bind(this._resume, this),this.getPollInterval());
this._interval = setInterval(JSJaC.bind(this._checkQueue, this),
JSJAC_CHECKQUEUEINTERVAL);
this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
JSJAC_CHECKINQUEUEINTERVAL);
}
return (this._connected === true);
} catch (e) {
if (e.message)
this.oDbg.log("Resume failed: "+e.message, 1);
else
this.oDbg.log("Resume failed: "+e, 1);
return false;
}
};
/**
* Sends a JSJaCPacket
* @param {JSJaCPacket} packet The packet to send
* @param {Function} cb The callback to be called if there's a reply
* to this packet (identified by id) [optional]
* @param {Object} arg Arguments passed to the callback
* (additionally to the packet received) [optional]
* @return 'true' if sending was successfull, 'false' otherwise
* @type boolean
*/
JSJaCConnection.prototype.send = function(packet,cb,arg) {
if (!packet || !packet.pType) {
this.oDbg.log("no packet: "+packet, 1);
return false;
}
if (!this.connected())
return false;
// generate an ID for the packet
if (!packet.getID())
packet.setID(genID());
// packet xml:lang
if (!packet.getXMLLang())
packet.setXMLLang(XML_LANG);
// remember id for response if callback present
if (cb)
this._registerPID(packet.getID(),cb,arg);
try {
this._handleEvent(packet.pType()+'_out', packet);
this._handleEvent("packet_out", packet);
this._pQueue = this._pQueue.concat(packet.xml());
} catch (e) {
this.oDbg.log(e.toString(),1);
return false;
}
return true;
};
/**
* Sends an IQ packet. Has default handlers for each reply type.
* Those maybe overriden by passing an appropriate handler.
* @param {JSJaCIQPacket} iq - the iq packet to send
* @param {Object} handlers - object with properties 'error_handler',
* 'result_handler' and 'default_handler'
* with appropriate functions
* @param {Object} arg - argument to handlers
* @return 'true' if sending was successfull, 'false' otherwise
* @type boolean
*/
JSJaCConnection.prototype.sendIQ = function(iq, handlers, arg) {
if (!iq || iq.pType() != 'iq') {
return false;
}
handlers = handlers || {};
var error_handler = handlers.error_handler || JSJaC.bind(function(aIq) {
this.oDbg.log(aIq.xml(), 1);
}, this);
var result_handler = handlers.result_handler || JSJaC.bind(function(aIq) {
this.oDbg.log(aIq.xml(), 2);
}, this);
var iqHandler = function(aIq, arg) {
switch (aIq.getType()) {
case 'error':
error_handler(aIq);
break;
case 'result':
result_handler(aIq, arg);
break;
}
};
return this.send(iq, iqHandler, arg);
};
/**
* Sets polling interval for this connection
* @param {int} millisecs Milliseconds to set timer to
* @return effective interval this connection has been set to
* @type int
*/
JSJaCConnection.prototype.setPollInterval = function(timerval) {
if (timerval && !isNaN(timerval))
this._timerval = timerval;
return this._timerval;
};
/**
* Returns current status of this connection
* @return String to denote current state. One of
* <ul>
* <li>'initializing' ... well
* <li>'connecting' if connect() was called
* <li>'resuming' if resume() was called
* <li>'processing' if it's about to operate as normal
* <li>'onerror_fallback' if there was an error with the request object
* <li>'protoerror_fallback' if there was an error at the http binding protocol flow (most likely that's where you interested in)
* <li>'internal_server_error' in case of an internal server error
* <li>'suspending' if suspend() is being called
* <li>'aborted' if abort() was called
* <li>'disconnecting' if disconnect() has been called
* </ul>
* @type String
*/
JSJaCConnection.prototype.status = function() { return this._status; };
/**
* Suspends this connection (saving state for later resume)
* Saves state to cookie
* @return Whether suspend (saving to cookie) was successful
* @type boolean
*/
JSJaCConnection.prototype.suspend = function(has_pause) {
var data = this.suspendToData(has_pause);
try {
var c = setDB('jsjac', 'state', JSJaCJSON.toString(data));
return c;
} catch (e) {
this.oDbg.log("Failed creating cookie '"+this._cookie_prefix+
"JSJaC_State': "+e.message,1);
}
return false;
};
/**
* Suspend connection and return serialized JSJaC connection state
* @return JSJaC connection state object
* @type Object
*/
JSJaCConnection.prototype.suspendToData = function(has_pause) {
// remove timers
if(has_pause) {
clearTimeout(this._timeout);
clearInterval(this._interval);
clearInterval(this._inQto);
this._suspend();
}
var u = ('_connected,_keys,_ID,_inQ,_pQueue,_regIDs,_errcnt,_inactivity,domain,username,resource,jid,fulljid,_sid,_httpbase,_timerval,_is_polling').split(',');
u = u.concat(this._getSuspendVars());
var s = new Object();
for (var i=0; i<u.length; i++) {
if (!this[u[i]]) continue; // hu? skip these!
if (this[u[i]]._getSuspendVars) {
var uo = this[u[i]]._getSuspendVars();
var o = new Object();
for (var j=0; j<uo.length; j++)
o[uo[j]] = this[u[i]][uo[j]];
} else
var o = this[u[i]];
s[u[i]] = o;
}
if(has_pause) {
this._connected = false;
this._setStatus('suspending');
}
return s;
};
/**
* @private
*/
JSJaCConnection.prototype._abort = function() {
clearTimeout(this._timeout); // remove timer
clearInterval(this._inQto);
clearInterval(this._interval);
this._connected = false;
this._setStatus('aborted');
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
this._handleEvent('onerror',
JSJaCError('500','cancel','service-unavailable'));
};
/**
* @private
*/
JSJaCConnection.prototype._checkInQ = function() {
for (var i=0; i<this._inQ.length && i<10; i++) {
var item = this._inQ[0];
this._inQ = this._inQ.slice(1,this._inQ.length);
var packet = JSJaCPacket.wrapNode(item);
if (!packet)
return;
this._handleEvent("packet_in", packet);
if (packet.pType && !this._handlePID(packet)) {
this._handleEvent(packet.pType()+'_in',packet);
this._handleEvent(packet.pType(),packet);
}
}
};
/**
* @private
*/
JSJaCConnection.prototype._checkQueue = function() {
if (this._pQueue.length != 0)
this._process();
return true;
};
/**
* @private
*/
JSJaCConnection.prototype._doAuth = function() {
if (this.has_sasl && this.authtype == 'nonsasl')
this.oDbg.log("Warning: SASL present but not used", 1);
if (!this._doSASLAuth() &&
!this._doLegacyAuth()) {
this.oDbg.log("Auth failed for authtype "+this.authtype,1);
this.disconnect();
return false;
}
return true;
};
/**
* @private
*/
JSJaCConnection.prototype._doInBandReg = function() {
if (this.authtype == 'saslanon' || this.authtype == 'anonymous')
return; // bullshit - no need to register if anonymous
/* ***
* In-Band Registration see JEP-0077
*/
var iq = new JSJaCIQ();
iq.setType('set');
iq.setID('reg1');
iq.appendNode("query", {xmlns: "jabber:iq:register"},
[["username", this.username],
["password", this.pass]]);
this.send(iq,this._doInBandRegDone);
};
/**
* @private
*/
JSJaCConnection.prototype._doInBandRegDone = function(iq) {
if (iq && iq.getType() == 'error') { // we failed to register
this.oDbg.log("registration failed for "+this.username,0);
this._handleEvent('onerror',iq.getChild('error'));
return;
}
this.oDbg.log(this.username + " registered succesfully",0);
this._doAuth();
};
/**
* @private
*/
JSJaCConnection.prototype._doLegacyAuth = function() {
if (this.authtype != 'nonsasl' && this.authtype != 'anonymous')
return false;
/* ***
* Non-SASL Authentication as described in JEP-0078
*/
var iq = new JSJaCIQ();
iq.setIQ(null,'get','auth1');
iq.appendNode('query', {xmlns: 'jabber:iq:auth'},
[['username', this.username]]);
this.send(iq,this._doLegacyAuth2);
return true;
};
/**
* @private
*/
JSJaCConnection.prototype._doLegacyAuth2 = function(iq) {
if (!iq || iq.getType() != 'result') {
if (iq && iq.getType() == 'error')
this._handleEvent('onerror',iq.getChild('error'));
this.disconnect();
return;
}
var use_digest = (iq.getChild('digest') != null);
/* ***
* Send authentication
*/
var iq = new JSJaCIQ();
iq.setIQ(null,'set','auth2');
query = iq.appendNode('query', {xmlns: 'jabber:iq:auth'},
[['username', this.username],
['resource', this.resource]]);
if (use_digest) { // digest login
query.appendChild(iq.buildNode('digest', {xmlns: 'jabber:iq:auth'},
hex_sha1(this.streamid + this.pass)));
} else if (this.allow_plain) { // use plaintext auth
query.appendChild(iq.buildNode('password', {xmlns: 'jabber:iq:auth'},
this.pass));
} else {
this.oDbg.log("no valid login mechanism found",1);
this.disconnect();
return false;
}
this.send(iq,this._doLegacyAuthDone);
};
/**
* @private
*/
JSJaCConnection.prototype._doLegacyAuthDone = function(iq) {
if (iq.getType() != 'result') { // auth' failed
if (iq.getType() == 'error')
this._handleEvent('onerror',iq.getChild('error'));
this.disconnect();
} else
this._handleEvent('onconnect');
};
/**
* @private
*/
JSJaCConnection.prototype._doSASLAuth = function() {
if (this.authtype == 'nonsasl' || this.authtype == 'anonymous')
return false;
if (this.authtype == 'saslanon') {
if (this.mechs['ANONYMOUS']) {
this.oDbg.log("SASL using mechanism 'ANONYMOUS'",2);
return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>",
this._doSASLAuthDone);
}
this.oDbg.log("SASL ANONYMOUS requested but not supported",1);
} else {
if (this.mechs['DIGEST-MD5']) {
this.oDbg.log("SASL using mechanism 'DIGEST-MD5'",2);
return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>",
this._doSASLAuthDigestMd5S1);
} else if (this.allow_plain && this.mechs['PLAIN']) {
this.oDbg.log("SASL using mechanism 'PLAIN'",2);
var authStr = this.username+'@'+
this.domain+String.fromCharCode(0)+
this.username+String.fromCharCode(0)+
this.pass;
this.oDbg.log("authenticating with '"+authStr+"'",2);
authStr = b64encode(authStr);
return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"+authStr+"</auth>",
this._doSASLAuthDone);
}
this.oDbg.log("No SASL mechanism applied",1);
this.authtype = 'nonsasl'; // fallback
}
return false;
};
/**
* @private
*/
JSJaCConnection.prototype._doSASLAuthDigestMd5S1 = function(el) {
if (el.nodeName != "challenge") {
this.oDbg.log("challenge missing",1);
this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
this.disconnect();
} else {
var challenge = b64decode(el.firstChild.nodeValue);
this.oDbg.log("got challenge: "+challenge,2);
this._nonce = challenge.substring(challenge.indexOf("nonce=")+7);
this._nonce = this._nonce.substring(0,this._nonce.indexOf("\""));
this.oDbg.log("nonce: "+this._nonce,2);
if (this._nonce == '' || this._nonce.indexOf('\"') != -1) {
this.oDbg.log("nonce not valid, aborting",1);
this.disconnect();
return;
}
this._digest_uri = "xmpp/";
// if (typeof(this.host) != 'undefined' && this.host != '') {
// this._digest-uri += this.host;
// if (typeof(this.port) != 'undefined' && this.port)
// this._digest-uri += ":" + this.port;
// this._digest-uri += '/';
// }
this._digest_uri += this.domain;
this._cnonce = cnonce(14);
this._nc = '00000001';
var X = this.username+':'+this.domain+':'+this.pass;
var Y = rstr_md5(str2rstr_utf8(X));
var A1 = Y+':'+this._nonce+':'+this._cnonce;
var HA1 = rstr2hex(rstr_md5(A1));
var A2 = 'AUTHENTICATE:'+this._digest_uri;
var HA2 = hex_md5(A2);
var response = hex_md5(HA1+':'+this._nonce+':'+this._nc+':'+
this._cnonce+':auth:'+HA2);
var rPlain = 'username="'+this.username+'",realm="'+this.domain+
'",nonce="'+this._nonce+'",cnonce="'+this._cnonce+'",nc="'+this._nc+
'",qop=auth,digest-uri="'+this._digest_uri+'",response="'+response+
'",charset="utf-8"';
this.oDbg.log("response: "+rPlain,2);
this._sendRaw("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"+
b64encode(rPlain)+"</response>",
this._doSASLAuthDigestMd5S2);
}
};
/**
* @private
*/
JSJaCConnection.prototype._doSASLAuthDigestMd5S2 = function(el) {
if (el.nodeName == 'failure') {
if (el.xml)
this.oDbg.log("auth error: "+el.xml,1);
else
this.oDbg.log("auth error",1);
this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
this.disconnect();
return;
}
var response = b64decode(el.firstChild.nodeValue);
this.oDbg.log("response: "+response,2);
var rspauth = response.substring(response.indexOf("rspauth=")+8);
this.oDbg.log("rspauth: "+rspauth,2);
var X = this.username+':'+this.domain+':'+this.pass;
var Y = rstr_md5(str2rstr_utf8(X));
var A1 = Y+':'+this._nonce+':'+this._cnonce;
var HA1 = rstr2hex(rstr_md5(A1));
var A2 = ':'+this._digest_uri;
var HA2 = hex_md5(A2);
var rsptest = hex_md5(HA1+':'+this._nonce+':'+this._nc+':'+
this._cnonce+':auth:'+HA2);
this.oDbg.log("rsptest: "+rsptest,2);
if (rsptest != rspauth) {
this.oDbg.log("SASL Digest-MD5: server repsonse with wrong rspauth",1);
this.disconnect();
return;
}
if (el.nodeName == 'success') {
this._reInitStream(JSJaC.bind(this._doStreamBind, this));
} else { // some extra turn
this._sendRaw("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>",
this._doSASLAuthDone);
}
};
/**
* @private
*/
JSJaCConnection.prototype._doSASLAuthDone = function (el) {
if (el.nodeName != 'success') {
this.oDbg.log("auth failed",1);
this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
this.disconnect();
} else {
this._reInitStream(JSJaC.bind(this._doStreamBind, this));
}
};
/**
* @private
*/
JSJaCConnection.prototype._doStreamBind = function() {
var iq = new JSJaCIQ();
iq.setIQ(null,'set','bind_1');
iq.appendNode("bind", {xmlns: "urn:ietf:params:xml:ns:xmpp-bind"},
[["resource", this.resource]]);
this.oDbg.log(iq.xml());
this.send(iq,this._doXMPPSess);
};
/**
* @private
*/
JSJaCConnection.prototype._doXMPPSess = function(iq) {
if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
this.disconnect();
if (iq.getType() == 'error')
this._handleEvent('onerror',iq.getChild('error'));
return;
}
this.fulljid = iq.getChildVal("jid");
this.jid = this.fulljid.substring(0,this.fulljid.lastIndexOf('/'));
iq = new JSJaCIQ();
iq.setIQ(null,'set','sess_1');
iq.appendNode("session", {xmlns: "urn:ietf:params:xml:ns:xmpp-session"},
[]);
this.oDbg.log(iq.xml());
this.send(iq,this._doXMPPSessDone);
};
/**
* @private
*/
JSJaCConnection.prototype._doXMPPSessDone = function(iq) {
if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
this.disconnect();
if (iq.getType() == 'error')
this._handleEvent('onerror',iq.getChild('error'));
return;
} else
this._handleEvent('onconnect');
};
/**
* @private
*/
JSJaCConnection.prototype._handleEvent = function(event,arg) {
event = event.toLowerCase(); // don't be case-sensitive here
this.oDbg.log("incoming event '"+event+"'",3);
if (!this._events[event])
return;
this.oDbg.log("handling event '"+event+"'",2);
for (var i=0;i<this._events[event].length; i++) {
var aEvent = this._events[event][i];
if (typeof aEvent.handler == 'function') {
try {
if (arg) {
if (arg.pType) { // it's a packet
if ((!arg.getNode().hasChildNodes() && aEvent.childName != '*') ||
(arg.getNode().hasChildNodes() &&
!arg.getChild(aEvent.childName, aEvent.childNS)))
continue;
if (aEvent.type != '*' &&
arg.getType() != aEvent.type)
continue;
this.oDbg.log(aEvent.childName+"/"+aEvent.childNS+"/"+aEvent.type+" => match for handler "+aEvent.handler,3);
}
if (aEvent.handler(arg)) {
// handled!
break;
}
}
else
if (aEvent.handler()) {
// handled!
break;
}
} catch (e) {
if (e.fileName&&e.lineNumber) {
this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message+' in '+e.fileName+' line '+e.lineNumber,1);
} else {
this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message,1);
}
}
}
}
};
/**
* @private
*/
JSJaCConnection.prototype._handlePID = function(aJSJaCPacket) {
if (!aJSJaCPacket.getID())
return false;
for (var i in this._regIDs) {
if (this._regIDs.hasOwnProperty(i) &&
this._regIDs[i] && i == aJSJaCPacket.getID()) {
var pID = aJSJaCPacket.getID();
this.oDbg.log("handling "+pID,3);
try {
if (this._regIDs[i].cb.call(this, aJSJaCPacket, this._regIDs[i].arg) === false) {
// don't unregister
return false;
} else {
this._unregisterPID(pID);
return true;
}
} catch (e) {
// broken handler?
this.oDbg.log(e.name+": "+ e.message, 1);
this._unregisterPID(pID);
return true;
}
}
}
return false;
};
/**
* @private
*/
JSJaCConnection.prototype._handleResponse = function(req) {
var rootEl = this._parseResponse(req);
if (!rootEl)
return;
for (var i=0; i<rootEl.childNodes.length; i++) {
if (this._sendRawCallbacks.length) {
var cb = this._sendRawCallbacks[0];
this._sendRawCallbacks = this._sendRawCallbacks.slice(1, this._sendRawCallbacks.length);
cb.fn.call(this, rootEl.childNodes.item(i), cb.arg);
continue;
}
this._inQ = this._inQ.concat(rootEl.childNodes.item(i));
}
};
/**
* @private
*/
JSJaCConnection.prototype._parseStreamFeatures = function(doc) {
if (!doc) {
this.oDbg.log("nothing to parse ... aborting",1);
return false;
}
var errorTag;
if (doc.getElementsByTagNameNS) {
errorTag = doc.getElementsByTagNameNS("http://etherx.jabber.org/streams", "error").item(0);
} else {
var errors = doc.getElementsByTagName("error");
for (var i=0; i<errors.length; i++)
if (errors.item(i).namespaceURI == "http://etherx.jabber.org/streams" ||
errors.item(i).getAttribute('xmlns') == "http://etherx.jabber.org/streams") {
errorTag = errors.item(i);
break;
}
}
if (errorTag) {
this._setStatus("internal_server_error");
clearTimeout(this._timeout); // remove timer
clearInterval(this._interval);
clearInterval(this._inQto);
this._handleEvent('onerror',JSJaCError('503','cancel','session-terminate'));
this._connected = false;
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
return false;
}
this.mechs = new Object();
var lMec1 = doc.getElementsByTagName("mechanisms");
this.has_sasl = false;
for (var i=0; i<lMec1.length; i++)
if (lMec1.item(i).getAttribute("xmlns") ==
"urn:ietf:params:xml:ns:xmpp-sasl") {
this.has_sasl=true;
var lMec2 = lMec1.item(i).getElementsByTagName("mechanism");
for (var j=0; j<lMec2.length; j++)
this.mechs[lMec2.item(j).firstChild.nodeValue] = true;
break;
}
if (this.has_sasl)
this.oDbg.log("SASL detected",2);
else {
this.oDbg.log("No support for SASL detected",2);
return false;
}
// Get the server CAPS (if available)
this.server_caps=null;
var sCaps = doc.getElementsByTagName("c");
for (var i=0; i<sCaps.length; i++) {
var c_sCaps=sCaps.item(i);
var x_sCaps=c_sCaps.getAttribute("xmlns");
var v_sCaps=c_sCaps.getAttribute("ver");
if ((x_sCaps == NS_CAPS) && v_sCaps) {
this.server_caps=v_sCaps;
break;
}
}
return true;
};
/**
* @private
*/
JSJaCConnection.prototype._process = function(timerval) {
if (!this.connected()) {
this.oDbg.log("Connection lost ...",1);
if (this._interval)
clearInterval(this._interval);
return;
}
this.setPollInterval(timerval);
if (this._timeout)
clearTimeout(this._timeout);
var slot = this._getFreeSlot();
if (slot < 0)
return;
if (typeof(this._req[slot]) != 'undefined' &&
typeof(this._req[slot].r) != 'undefined' &&
this._req[slot].r.readyState != 4) {
this.oDbg.log("Slot "+slot+" is not ready");
return;
}
if (!this.isPolling() && this._pQueue.length == 0 &&
this._req[(slot+1)%2] && this._req[(slot+1)%2].r.readyState != 4) {
this.oDbg.log("all slots busy, standby ...", 2);
return;
}
if (!this.isPolling())
this.oDbg.log("Found working slot at "+slot,2);
this._req[slot] = this._setupRequest(true);
/* setup onload handler for async send */
this._req[slot].r.onreadystatechange =
JSJaC.bind(function() {
if (this._req[slot].r.readyState == 4) {
this._setStatus('processing');
this.oDbg.log("async recv: "+this._req[slot].r.responseText,4);
this._handleResponse(this._req[slot]);
if (!this.connected())
return;
// schedule next tick
if (this._pQueue.length) {
this._timeout = setTimeout(JSJaC.bind(this._process, this),100);
} else {
this.oDbg.log("scheduling next poll in "+this.getPollInterval()+
" msec", 4);
this._timeout = setTimeout(JSJaC.bind(this._process, this),this.getPollInterval());
}
}
}, this);
try {
this._req[slot].r.onerror =
JSJaC.bind(function() {
if (!this.connected())
return;
this._errcnt++;
this.oDbg.log('XmlHttpRequest error ('+this._errcnt+')',1);
if (this._errcnt > JSJAC_ERR_COUNT) {
// abort
this._abort();
return false;
}
this._setStatus('onerror_fallback');
// schedule next tick
setTimeout(JSJaC.bind(this._resume, this),this.getPollInterval());
return false;
}, this);
} catch(e) { } // well ... no onerror property available, maybe we
// can catch the error somewhere else ...
var reqstr = this._getRequestString();
if (typeof(this._rid) != 'undefined') // remember request id if any
this._req[slot].rid = this._rid;
this.oDbg.log("sending: " + reqstr,4);
this._req[slot].r.send(reqstr);
};
/**
* @private
*/
JSJaCConnection.prototype._registerPID = function(pID,cb,arg) {
if (!pID || !cb)
return false;
this._regIDs[pID] = new Object();
this._regIDs[pID].cb = cb;
if (arg)
this._regIDs[pID].arg = arg;
this.oDbg.log("registered "+pID,3);
return true;
};
/**
* partial function binding sendEmpty to callback
* @private
*/
JSJaCConnection.prototype._prepSendEmpty = function(cb, ctx) {
return function() {
ctx._sendEmpty(JSJaC.bind(cb, ctx));
};
};
/**
* send empty request
* waiting for stream id to be able to proceed with authentication
* @private
*/
JSJaCConnection.prototype._sendEmpty = function(cb) {
var slot = this._getFreeSlot();
this._req[slot] = this._setupRequest(true);
this._req[slot].r.onreadystatechange =
JSJaC.bind(function() {
if (this._req[slot].r.readyState == 4) {
this.oDbg.log("async recv: "+this._req[slot].r.responseText,4);
cb(this._req[slot].r); // handle response
}
},this);
if (typeof(this._req[slot].r.onerror) != 'undefined') {
this._req[slot].r.onerror =
JSJaC.bind(function(e) {
this.oDbg.log('XmlHttpRequest error',1);
return false;
}, this);
}
var reqstr = this._getRequestString();
this.oDbg.log("sending: " + reqstr,4);
this._req[slot].r.send(reqstr);
};
/**
* @private
*/
JSJaCConnection.prototype._sendRaw = function(xml,cb,arg) {
if (cb)
this._sendRawCallbacks.push({fn: cb, arg: arg});
this._pQueue.push(xml);
this._process();
return true;
};
/**
* @private
*/
JSJaCConnection.prototype._setStatus = function(status) {
if (!status || status == '')
return;
if (status != this._status) { // status changed!
this._status = status;
this._handleEvent('onstatuschanged', status);
this._handleEvent('status_changed', status);
}
};
/**
* @private
*/
JSJaCConnection.prototype._unregisterPID = function(pID) {
if (!this._regIDs[pID])
return false;
this._regIDs[pID] = null;
this.oDbg.log("unregistered "+pID,3);
return true;
};
/**
* @fileoverview All stuff related to HTTP Binding
* @author Stefan Strigler steve@zeank.in-berlin.de
* @version $Revision$
*/
/**
* Instantiates an HTTP Binding session
* @class Implementation of {@link
* http://www.xmpp.org/extensions/xep-0206.html XMPP Over BOSH}
* formerly known as HTTP Binding.
* @extends JSJaCConnection
* @constructor
*/
function JSJaCHttpBindingConnection(oArg) {
/**
* @ignore
*/
this.base = JSJaCConnection;
this.base(oArg);
// member vars
/**
* @private
*/
this._hold = JSJACHBC_MAX_HOLD;
/**
* @private
*/
this._inactivity = 0;
/**
* @private
*/
this._last_requests = new Object(); // 'hash' storing hold+1 last requests
/**
* @private
*/
this._last_rid = 0; // I know what you did last summer
/**
* @private
*/
this._min_polling = 0;
/**
* @private
*/
this._pause = 0;
/**
* @private
*/
this._wait = JSJACHBC_MAX_WAIT;
}
JSJaCHttpBindingConnection.prototype = new JSJaCConnection();
/**
* Inherit an instantiated HTTP Binding session
*/
JSJaCHttpBindingConnection.prototype.inherit = function(oArg) {
if (oArg.jid) {
var oJid = new JSJaCJID(oArg.jid);
this.domain = oJid.getDomain();
this.username = oJid.getNode();
this.resource = oJid.getResource();
} else {
this.domain = oArg.domain || 'localhost';
this.username = oArg.username;
this.resource = oArg.resource;
}
this._sid = oArg.sid;
this._rid = oArg.rid;
this._min_polling = oArg.polling;
this._inactivity = oArg.inactivity;
this._setHold(oArg.requests-1);
this.setPollInterval(this._timerval);
if (oArg.wait)
this._wait = oArg.wait; // for whatever reason
this._connected = true;
this._handleEvent('onconnect');
this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
JSJAC_CHECKQUEUEINTERVAL);
this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
JSJAC_CHECKINQUEUEINTERVAL);
this._timeout = setTimeout(JSJaC.bind(this._process, this),
this.getPollInterval());
};
/**
* Sets poll interval
* @param {int} timerval the interval in seconds
*/
JSJaCHttpBindingConnection.prototype.setPollInterval = function(timerval) {
if (timerval && !isNaN(timerval)) {
if (!this.isPolling())
this._timerval = 100;
else if (this._min_polling && timerval < this._min_polling*1000)
this._timerval = this._min_polling*1000;
else if (this._inactivity && timerval > this._inactivity*1000)
this._timerval = this._inactivity*1000;
else
this._timerval = timerval;
}
return this._timerval;
};
/**
* whether this session is in polling mode
* @type boolean
*/
JSJaCHttpBindingConnection.prototype.isPolling = function() { return (this._hold == 0) };
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getFreeSlot = function() {
for (var i=0; i<this._hold+1; i++)
if (typeof(this._req[i]) == 'undefined' || typeof(this._req[i].r) == 'undefined' || this._req[i].r.readyState == 4)
return i;
return -1; // nothing found
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getHold = function() { return this._hold; };
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getRequestString = function(raw, last) {
raw = raw || '';
var reqstr = '';
// check if we're repeating a request
if (this._rid <= this._last_rid && typeof(this._last_requests[this._rid]) != 'undefined') // repeat!
reqstr = this._last_requests[this._rid].xml;
else { // grab from queue
var xml = '';
while (this._pQueue.length) {
var curNode = this._pQueue[0];
xml += curNode;
this._pQueue = this._pQueue.slice(1,this._pQueue.length);
}
reqstr = "<body xml:lang='"+XML_LANG+"' rid='"+this._rid+"' sid='"+this._sid+"' xmlns='http://jabber.org/protocol/httpbind' ";
if (JSJAC_HAVEKEYS) {
reqstr += "key='"+this._keys.getKey()+"' ";
if (this._keys.lastKey()) {
this._keys = new JSJaCKeys(hex_sha1,this.oDbg);
reqstr += "newkey='"+this._keys.getKey()+"' ";
}
}
if (last)
reqstr += "type='terminate'";
else if (this._reinit) {
if (JSJACHBC_USE_BOSH_VER)
reqstr += "xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh' to='"+this.domain+"'";
this._reinit = false;
}
if (xml != '' || raw != '') {
reqstr += ">" + raw + xml + "</body>";
} else {
reqstr += "/>";
}
this._last_requests[this._rid] = new Object();
this._last_requests[this._rid].xml = reqstr;
this._last_rid = this._rid;
for (var i in this._last_requests)
if (this._last_requests.hasOwnProperty(i) &&
i < this._rid-this._hold)
delete(this._last_requests[i]); // truncate
}
return reqstr;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getInitialRequestString = function() {
var reqstr = "<body xml:lang='"+XML_LANG+"' content='text/xml; charset=utf-8' hold='"+this._hold+"' xmlns='http://jabber.org/protocol/httpbind' to='"+this.authhost+"' wait='"+this._wait+"' rid='"+this._rid+"'";
if (this.secure)
reqstr += " secure='"+this.secure+"'";
if (JSJAC_HAVEKEYS) {
this._keys = new JSJaCKeys(hex_sha1,this.oDbg); // generate first set of keys
key = this._keys.getKey();
reqstr += " newkey='"+key+"'";
}
if (JSJACHBC_USE_BOSH_VER) {
reqstr += " ver='" + JSJACHBC_BOSH_VERSION + "'";
reqstr += " xmlns:xmpp='urn:xmpp:xbosh'";
if (this.authtype == 'sasl' || this.authtype == 'saslanon')
reqstr += " xmpp:version='1.0'";
}
reqstr += "/>";
return reqstr;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getStreamID = function(req) {
this.oDbg.log(req.responseText,4);
if (!req.responseXML || !req.responseXML.documentElement) {
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
var body = req.responseXML.documentElement;
// any session error?
if(body.getAttribute('type') == 'terminate') {
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
// extract stream id used for non-SASL authentication
if (body.getAttribute('authid')) {
this.streamid = body.getAttribute('authid');
this.oDbg.log("got streamid: "+this.streamid,2);
}
if (!this._parseStreamFeatures(body)) {
this._sendEmpty(JSJaC.bind(this._getStreamID, this));
return;
}
this._timeout = setTimeout(JSJaC.bind(this._process, this),
this.getPollInterval());
if (this.register)
this._doInBandReg();
else
this._doAuth();
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getSuspendVars = function() {
return ('host,port,secure,_rid,_last_rid,_wait,_min_polling,_inactivity,_hold,_last_requests,_pause').split(',');
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._handleInitialResponse = function(req) {
try {
// This will throw an error on Mozilla when the connection was refused
this.oDbg.log(req.getAllResponseHeaders(),4);
this.oDbg.log(req.responseText,4);
} catch(ex) {
this.oDbg.log("No response",4);
}
if (req.status != 200 || !req.responseXML) {
this.oDbg.log("initial response broken (status: "+req.status+")",1);
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
var body = req.responseXML.documentElement;
if (!body || body.tagName != 'body' || body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
this.oDbg.log("no body element or incorrect body in initial response",1);
this._handleEvent("onerror",JSJaCError("500","wait","internal-service-error"));
return;
}
// Check for errors from the server
if (body.getAttribute("type") == "terminate") {
this.oDbg.log("invalid response:\n" + req.responseText,1);
clearTimeout(this._timeout); // remove timer
this._connected = false;
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
// get session ID
this._sid = body.getAttribute('sid');
this.oDbg.log("got sid: "+this._sid,2);
// get attributes from response body
if (body.getAttribute('polling'))
this._min_polling = body.getAttribute('polling');
if (body.getAttribute('inactivity'))
this._inactivity = body.getAttribute('inactivity');
if (body.getAttribute('requests'))
this._setHold(body.getAttribute('requests')-1);
this.oDbg.log("set hold to " + this._getHold(),2);
if (body.getAttribute('ver'))
this._bosh_version = body.getAttribute('ver');
if (body.getAttribute('maxpause'))
this._pause = Number.min(body.getAttribute('maxpause'), JSJACHBC_MAXPAUSE);
// must be done after response attributes have been collected
this.setPollInterval(this._timerval);
/* start sending from queue for not polling connections */
this._connected = true;
this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
JSJAC_CHECKINQUEUEINTERVAL);
this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
JSJAC_CHECKQUEUEINTERVAL);
/* wait for initial stream response to extract streamid needed
* for digest auth
*/
this._getStreamID(req);
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._parseResponse = function(req) {
if (!this.connected() || !req)
return null;
var r = req.r; // the XmlHttpRequest
try {
if (r.status == 404 || r.status == 403) {
// connection manager killed session
this._abort();
return null;
}
if (r.status != 200 || !r.responseXML) {
this._errcnt++;
var errmsg = "invalid response ("+r.status+"):\n" + r.getAllResponseHeaders()+"\n"+r.responseText;
if (!r.responseXML)
errmsg += "\nResponse failed to parse!";
this.oDbg.log(errmsg,1);
if (this._errcnt > JSJAC_ERR_COUNT) {
// abort
this._abort();
return null;
}
if (this.connected()) {
this.oDbg.log("repeating ("+this._errcnt+")",1);
this._setStatus('proto_error_fallback');
// schedule next tick
setTimeout(JSJaC.bind(this._resume, this),
this.getPollInterval());
}
return null;
}
} catch (e) {
this.oDbg.log("XMLHttpRequest error: status not available", 1);
this._errcnt++;
if (this._errcnt > JSJAC_ERR_COUNT) {
// abort
this._abort();
} else {
if (this.connected()) {
this.oDbg.log("repeating ("+this._errcnt+")",1);
this._setStatus('proto_error_fallback');
// schedule next tick
setTimeout(JSJaC.bind(this._resume, this),
this.getPollInterval());
}
}
return null;
}
var body = r.responseXML.documentElement;
if (!body || body.tagName != 'body' ||
body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
this.oDbg.log("invalid response:\n" + r.responseText,1);
clearTimeout(this._timeout); // remove timer
clearInterval(this._interval);
clearInterval(this._inQto);
this._connected = false;
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
this._setStatus('internal_server_error');
this._handleEvent('onerror',
JSJaCError('500','wait','internal-server-error'));
return null;
}
if (typeof(req.rid) != 'undefined' && this._last_requests[req.rid]) {
if (this._last_requests[req.rid].handled) {
this.oDbg.log("already handled "+req.rid,2);
return null;
} else
this._last_requests[req.rid].handled = true;
}
// Check for errors from the server
if (body.getAttribute("type") == "terminate") {
// read condition
var condition = body.getAttribute('condition');
if (condition != "item-not-found") {
this.oDbg.log("session terminated:\n" + r.responseText,1);
clearTimeout(this._timeout); // remove timer
clearInterval(this._interval);
clearInterval(this._inQto);
try {
removeDB('jsjac', 'state');
} catch (e) {}
this._connected = false;
if (condition == "remote-stream-error")
if (body.getElementsByTagName("conflict").length > 0)
this._setStatus("session-terminate-conflict");
if (condition == null)
condition = 'session-terminate';
this._handleEvent('onerror',JSJaCError('503','cancel',condition));
this.oDbg.log("Aborting remaining connections",4);
for (var i=0; i<this._hold+1; i++) {
try {
this._req[i].r.abort();
} catch(e) { this.oDbg.log(e, 1); }
}
this.oDbg.log("parseResponse done with terminating", 3);
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
} else {
this._errcnt++;
if (this._errcnt > JSJAC_ERR_COUNT)
this._abort();
}
return null;
}
// no error
this._errcnt = 0;
return r.responseXML.documentElement;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._reInitStream = function(cb) {
// tell http binding to reinit stream with/before next request
this._reinit = true;
this._sendEmpty(this._prepReInitStreamWait(cb));
};
JSJaCHttpBindingConnection.prototype._prepReInitStreamWait = function(cb) {
return JSJaC.bind(function(req) {
this._reInitStreamWait(req, cb);
}, this);
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._reInitStreamWait = function(req, cb) {
this.oDbg.log("checking for stream features");
var doc = req.responseXML.documentElement;
this.oDbg.log(doc);
if (doc.getElementsByTagNameNS) {
this.oDbg.log("checking with namespace");
var features = doc.getElementsByTagNameNS('http://etherx.jabber.org/streams',
'features').item(0);
if (features) {
var bind = features.getElementsByTagNameNS('urn:ietf:params:xml:ns:xmpp-bind',
'bind').item(0);
}
} else {
var featuresNL = doc.getElementsByTagName('stream:features');
for (var i=0, l=featuresNL.length; i<l; i++) {
if (featuresNL.item(i).namespaceURI == 'http://etherx.jabber.org/streams' ||
featuresNL.item(i).getAttribute('xmlns') ==
'http://etherx.jabber.org/streams') {
var features = featuresNL.item(i);
break;
}
}
if (features) {
var bind = features.getElementsByTagName('bind');
for (var i=0, l=bind.length; i<l; i++) {
if (bind.item(i).namespaceURI == 'urn:ietf:params:xml:ns:xmpp-bind' ||
bind.item(i).getAttribute('xmlns') ==
'urn:ietf:params:xml:ns:xmpp-bind') {
bind = bind.item(i);
break;
}
}
}
}
this.oDbg.log(features);
this.oDbg.log(bind);
if (features) {
if (bind) {
cb();
} else {
this.oDbg.log("no bind feature - giving up",1);
this._handleEvent('onerror',JSJaCError('503','cancel',"service-unavailable"));
this._connected = false;
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
}
} else {
// wait
this._sendEmpty(this._prepReInitStreamWait(cb));
}
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._resume = function() {
/* make sure to repeat last request as we can be sure that
* it had failed (only if we're not using the 'pause' attribute
*/
if (this._pause == 0 && this._rid >= this._last_rid)
this._rid = this._last_rid-1;
this._process();
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._setHold = function(hold) {
if (!hold || isNaN(hold) || hold < 0)
hold = 0;
else if (hold > JSJACHBC_MAX_HOLD)
hold = JSJACHBC_MAX_HOLD;
this._hold = hold;
return this._hold;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._setupRequest = function(async) {
var req = new Object();
var r = XmlHttp.create();
try {
r.open("POST",this._httpbase,async);
r.setRequestHeader('Content-Type','text/xml; charset=utf-8');
} catch(e) { this.oDbg.log(e,1); }
req.r = r;
this._rid++;
req.rid = this._rid;
return req;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._suspend = function() {
if (this._pause == 0)
return; // got nothing to do
var slot = this._getFreeSlot();
// Intentionally synchronous
this._req[slot] = this._setupRequest(false);
var reqstr = "<body xml:lang='"+XML_LANG+"' pause='"+this._pause+"' xmlns='http://jabber.org/protocol/httpbind' sid='"+this._sid+"' rid='"+this._rid+"'";
if (JSJAC_HAVEKEYS) {
reqstr += " key='"+this._keys.getKey()+"'";
if (this._keys.lastKey()) {
this._keys = new JSJaCKeys(hex_sha1,this.oDbg);
reqstr += " newkey='"+this._keys.getKey()+"'";
}
}
reqstr += ">";
while (this._pQueue.length) {
var curNode = this._pQueue[0];
reqstr += curNode;
this._pQueue = this._pQueue.slice(1,this._pQueue.length);
}
//reqstr += "<presence type='unavailable' xmlns='jabber:client'/>";
reqstr += "</body>";
this.oDbg.log("Disconnecting: " + reqstr,4);
this._req[slot].r.send(reqstr);
};
/*
Jappix - An open social platform
These are the constants JS scripts for Jappix
-------------------------------------------------
License: dual-licensed under AGPL and MPLv2
Authors: Stefan Strigler, Valérian Saliou, Kloadut
Last revision: 07/06/13
*/
// XMPP XMLNS attributes
var NS_PROTOCOL = 'http://jabber.org/protocol/';
var NS_FEATURES = 'http://jabber.org/features/';
var NS_CLIENT = 'jabber:client';
var NS_IQ = 'jabber:iq:';
var NS_X = 'jabber:x:';
var NS_IETF = 'urn:ietf:params:xml:ns:xmpp-';
var NS_XMPP = 'urn:xmpp:';
var NS_STORAGE = 'storage:';
var NS_BOOKMARKS = NS_STORAGE + 'bookmarks';
var NS_ROSTERNOTES = NS_STORAGE + 'rosternotes';
var NS_JAPPIX = 'jappix:';
var NS_INBOX = NS_JAPPIX + 'inbox';
var NS_OPTIONS = NS_JAPPIX + 'options';
var NS_DISCO_ITEMS = NS_PROTOCOL + 'disco#items';
var NS_DISCO_INFO = NS_PROTOCOL + 'disco#info';
var NS_VCARD = 'vcard-temp';
var NS_VCARD_P = NS_VCARD + ':x:update';
var NS_AUTH = NS_IQ + 'auth';
var NS_AUTH_ERROR = NS_IQ + 'auth:error';
var NS_REGISTER = NS_IQ + 'register';
var NS_SEARCH = NS_IQ + 'search';
var NS_ROSTER = NS_IQ + 'roster';
var NS_PRIVACY = NS_IQ + 'privacy';
var NS_PRIVATE = NS_IQ + 'private';
var NS_VERSION = NS_IQ + 'version';
var NS_TIME = NS_IQ + 'time';
var NS_LAST = NS_IQ + 'last';
var NS_IQDATA = NS_IQ + 'data';
var NS_XDATA = NS_X + 'data';
var NS_IQOOB = NS_IQ + 'oob';
var NS_XOOB = NS_X + 'oob';
var NS_DELAY = NS_X + 'delay';
var NS_EXPIRE = NS_X + 'expire';
var NS_EVENT = NS_X + 'event';
var NS_XCONFERENCE = NS_X + 'conference';
var NS_STATS = NS_PROTOCOL + 'stats';
var NS_MUC = NS_PROTOCOL + 'muc';
var NS_MUC_USER = NS_MUC + '#user';
var NS_MUC_ADMIN = NS_MUC + '#admin';
var NS_MUC_OWNER = NS_MUC + '#owner';
var NS_MUC_CONFIG = NS_MUC + '#roomconfig';
var NS_PUBSUB = NS_PROTOCOL + 'pubsub';
var NS_PUBSUB_EVENT = NS_PUBSUB + '#event';
var NS_PUBSUB_OWNER = NS_PUBSUB + '#owner';
var NS_PUBSUB_NMI = NS_PUBSUB + '#node-meta-info';
var NS_PUBSUB_NC = NS_PUBSUB + '#node_config';
var NS_PUBSUB_CN = NS_PUBSUB + '#config-node';
var NS_PUBSUB_RI = NS_PUBSUB + '#retrieve-items';
var NS_COMMANDS = NS_PROTOCOL + 'commands';
var NS_BOSH = NS_PROTOCOL + 'httpbind';
var NS_STREAM = 'http://etherx.jabber.org/streams';
var NS_URN_TIME = NS_XMPP + 'time';
var NS_URN_PING = NS_XMPP + 'ping';
var NS_URN_ADATA = NS_XMPP + 'avatar:data';
var NS_URN_AMETA = NS_XMPP + 'avatar:metadata';
var NS_URN_MBLOG = NS_XMPP + 'microblog:0';
var NS_URN_INBOX = NS_XMPP + 'inbox';
var NS_URN_FORWARD = NS_XMPP + 'forward:0';
var NS_URN_MAM = NS_XMPP + 'mam:0';
var NS_URN_DELAY = NS_XMPP + 'delay';
var NS_URN_RECEIPTS = NS_XMPP + 'receipts';
var NS_RSM = NS_PROTOCOL + 'rsm';
var NS_IPV6 = 'ipv6';
var NS_XHTML = 'http://www.w3.org/1999/xhtml';
var NS_XHTML_IM = NS_PROTOCOL + 'xhtml-im';
var NS_CHATSTATES = NS_PROTOCOL + 'chatstates';
var NS_HTTP_AUTH = NS_PROTOCOL + 'http-auth';
var NS_ROSTERX = NS_PROTOCOL + 'rosterx';
var NS_MOOD = NS_PROTOCOL + 'mood';
var NS_ACTIVITY = NS_PROTOCOL + 'activity';
var NS_TUNE = NS_PROTOCOL + 'tune';
var NS_GEOLOC = NS_PROTOCOL + 'geoloc';
var NS_NICK = NS_PROTOCOL + 'nick';
var NS_NOTIFY = '+notify';
var NS_CAPS = NS_PROTOCOL + 'caps';
var NS_ATOM = 'http://www.w3.org/2005/Atom';
var NS_STANZAS = NS_IETF + 'stanzas';
var NS_STREAMS = NS_IETF + 'streams';
var NS_TLS = NS_IETF + 'tls';
var NS_SASL = NS_IETF + 'sasl';
var NS_SESSION = NS_IETF + 'session';
var NS_BIND = NS_IETF + 'bind';
var NS_FEATURE_IQAUTH = NS_FEATURES + 'iq-auth';
var NS_FEATURE_IQREGISTER = NS_FEATURES + 'iq-register';
var NS_FEATURE_COMPRESS = NS_FEATURES + 'compress';
var NS_COMPRESS = NS_PROTOCOL + 'compress';
// Available locales
var LOCALES_AVAILABLE_ID = new Array();
var LOCALES_AVAILABLE_NAMES = new Array();
// XML lang
var XML_LANG = null;
// Jappix parameters
var JAPPIX_STATIC = null;
var JAPPIX_VERSION = null;
var JAPPIX_MAX_FILE_SIZE = null;
var JAPPIX_MAX_UPLOAD = null;
// Jappix main configuration
var SERVICE_NAME = null;
var SERVICE_DESC = null;
var OWNER_NAME = null;
var OWNER_WEBSITE = null;
var LEGAL = null;
var JAPPIX_RESOURCE = null;
var LOCK_HOST = null;
var ANONYMOUS = null;
var HTTP_AUTH = null;
var REGISTRATION = null;
var BOSH_PROXY = null;
var MANAGER_LINK = null;
var GROUPCHATS_JOIN = null;
var GROUPCHATS_SUGGEST = null;
var ENCRYPTION = null;
var HTTPS_STORAGE = null;
var HTTPS_FORCE = null;
var COMPRESSION = null;
var MULTI_FILES = null;
var DEVELOPER = null;
var REGISTER_API = null;
// Jappix hosts configuration
var HOST_MAIN = null;
var HOST_MUC = null;
var HOST_PUBSUB = null;
var HOST_VJUD = null;
var HOST_ANONYMOUS = null;
var HOST_BOSH = null;
var HOST_BOSH_MAIN = null;
var HOST_BOSH_MINI = null;
var HOST_STATIC = null;
var HOST_UPLOAD = null;
// Anonymous mode
var ANONYMOUS_ROOM = null;
var ANONYMOUS_NICK = null;
// Node parameters
var JAPPIX_LOCATION = getJappixLocation();
var BOSH_SAME_ORIGIN = false;
// XMPP error stanzas
function STANZA_ERROR(code, type, cond) {
if (window == this)
return new STANZA_ERROR(code, type, cond);
this.code = code;
this.type = type;
this.cond = cond;
}
var ERR_BAD_REQUEST =
STANZA_ERROR('400', 'modify', 'bad-request');
var ERR_CONFLICT =
STANZA_ERROR('409', 'cancel', 'conflict');
var ERR_FEATURE_NOT_IMPLEMENTED =
STANZA_ERROR('501', 'cancel', 'feature-not-implemented');
var ERR_FORBIDDEN =
STANZA_ERROR('403', 'auth', 'forbidden');
var ERR_GONE =
STANZA_ERROR('302', 'modify', 'gone');
var ERR_INTERNAL_SERVER_ERROR =
STANZA_ERROR('500', 'wait', 'internal-server-error');
var ERR_ITEM_NOT_FOUND =
STANZA_ERROR('404', 'cancel', 'item-not-found');
var ERR_JID_MALFORMED =
STANZA_ERROR('400', 'modify', 'jid-malformed');
var ERR_NOT_ACCEPTABLE =
STANZA_ERROR('406', 'modify', 'not-acceptable');
var ERR_NOT_ALLOWED =
STANZA_ERROR('405', 'cancel', 'not-allowed');
var ERR_NOT_AUTHORIZED =
STANZA_ERROR('401', 'auth', 'not-authorized');
var ERR_PAYMENT_REQUIRED =
STANZA_ERROR('402', 'auth', 'payment-required');
var ERR_RECIPIENT_UNAVAILABLE =
STANZA_ERROR('404', 'wait', 'recipient-unavailable');
var ERR_REDIRECT =
STANZA_ERROR('302', 'modify', 'redirect');
var ERR_REGISTRATION_REQUIRED =
STANZA_ERROR('407', 'auth', 'registration-required');
var ERR_REMOTE_SERVER_NOT_FOUND =
STANZA_ERROR('404', 'cancel', 'remote-server-not-found');
var ERR_REMOTE_SERVER_TIMEOUT =
STANZA_ERROR('504', 'wait', 'remote-server-timeout');
var ERR_RESOURCE_CONSTRAINT =
STANZA_ERROR('500', 'wait', 'resource-constraint');
var ERR_SERVICE_UNAVAILABLE =
STANZA_ERROR('503', 'cancel', 'service-unavailable');
var ERR_SUBSCRIPTION_REQUIRED =
STANZA_ERROR('407', 'auth', 'subscription-required');
var ERR_UNEXPECTED_REQUEST =
STANZA_ERROR('400', 'wait', 'unexpected-request');
/*
Jappix - An open social platform
These are the temporary/persistent data store functions
-------------------------------------------------
License: dual-licensed under AGPL and MPLv2
Authors: Valérian Saliou, Maranda
Last revision: 20/07/13
*/
// Common: storage adapter
function storageAdapter(storage_native, storage_emulated) {
var legacy = !storage_native;
this.key = function(key) {
if(legacy) {
if(key >= this.length)
return null;
var c = 0;
for(name in storage_emulated) {
if(c++ == key) return name;
}
return null;
}
return storage_native.key(key);
};
this.getItem = function(key) {
if(legacy) {
if(storage_emulated[key] !== undefined)
return storage_emulated[key];
return null;
} else {
return storage_native.getItem(key);
}
};
this.setItem = function(key, data) {
if(legacy) {
if(!(key in storage_emulated))
this.length++;
storage_emulated[key] = (data + '');
} else {
storage_native.setItem(key, data);
this.length = storage_native.length;
}
};
this.removeItem = function(key) {
if(legacy) {
if(key in storage_emulated) {
this.length--;
delete storage_emulated[key];
}
} else {
storage_native.removeItem(key);
this.length = storage_native.length;
}
};
this.clear = function() {
if(legacy) {
this.length = 0;
storage_emulated = {};
} else {
storage_native.clear();
this.length = storage_native.length;
}
};
this.length = legacy ? 0 : storage_native.length;
}
// Temporary: sessionStorage emulation
var DATASTORE_DB_EMULATED = {};
// Temporary: sessionStorage class alias for direct access
var storageDB = new storageAdapter(
(window.sessionStorage ? sessionStorage : null),
DATASTORE_DB_EMULATED
);
// Temporary: returns whether it is available or not
function hasDB() {
// Try to write something
try {
storageDB.setItem('hasdb_check', 'ok');
storageDB.removeItem('hasdb_check');
return true;
}
// Not available?
catch(e) {
return false;
}
}
// Temporary: used to read a database entry
function getDB(type, id) {
try {
return storageDB.getItem(type + '_' + id);
}
catch(e) {
logThis('Error while getting a temporary database entry (' + type + ' -> ' + id + '): ' + e, 1);
}
return null;
}
// Temporary: used to update a database entry
function setDB(type, id, value) {
try {
storageDB.setItem(type + '_' + id, value);
return true;
}
catch(e) {
logThis('Error while writing a temporary database entry (' + type + ' -> ' + id + '): ' + e, 1);
}
return false;
}
// Temporary: used to remove a database entry
function removeDB(type, id) {
try {
storageDB.removeItem(type + '_' + id);
return true;
}
catch(e) {
logThis('Error while removing a temporary database entry (' + type + ' -> ' + id + '): ' + e, 1);
}
return false;
}
// Temporary: used to check a database entry exists
function existDB(type, id) {
return getDB(type, id) != null;
}
// Temporary: used to clear all the database
function resetDB() {
try {
storageDB.clear();
logThis('Temporary database cleared.', 3);
return true;
}
catch(e) {
logThis('Error while clearing temporary database: ' + e, 1);
return false;
}
}
// Persistent: localStorage emulation
var DATASTORE_PERSISTENT_EMULATED = {};
// Persistent: localStorage class alias for direct access
var storagePersistent = new storageAdapter(
(window.localStorage ? localStorage : null),
DATASTORE_PERSISTENT_EMULATED
);
// Persistent: returns whether it is available or not
function hasPersistent() {
// Try to write something
try {
storagePersistent.setItem('haspersistent_check', 'ok');
storagePersistent.removeItem('haspersistent_check');
return true;
}
// Not available?
catch(e) {
return false;
}
}
// Persistent: used to read a database entry
function getPersistent(dbID, type, id) {
try {
return storagePersistent.getItem(dbID + '_' + type + '_' + id);
}
catch(e) {
logThis('Error while getting a persistent database entry (' + dbID + ' -> ' + type + ' -> ' + id + '): ' + e, 1);
return null;
}
}
// Persistent: used to update a database entry
function setPersistent(dbID, type, id, value) {
try {
storagePersistent.setItem(dbID + '_' + type + '_' + id, value);
return true;
}
// Database might be full
catch(e) {
logThis('Retrying: could not write a persistent database entry (' + dbID + ' -> ' + type + ' -> ' + id + '): ' + e, 2);
// Flush it!
flushPersistent();
// Set the item again
try {
storagePersistent.setItem(dbID + ' -> ' + type + '_' + id, value);
return true;
}
// New error!
catch(e) {
logThis('Aborted: error while writing a persistent database entry (' + dbID + ' -> ' + type + ' -> ' + id + '): ' + e, 1);
}
}
return false;
}
// Persistent: used to remove a database entry
function removePersistent(dbID, type, id) {
try {
storagePersistent.removeItem(dbID + '_' + type + '_' + id);
return true;
}
catch(e) {
logThis('Error while removing a persistent database entry (' + dbID + ' -> ' + type + ' -> ' + id + '): ' + e, 1);
}
return false;
}
// Persistent: used to check a database entry exists
function existPersistent(dbID, type, id) {
return getPersistent(dbID, type, id) != null;
}
// Persistent: used to clear all the database
function resetPersistent() {
try {
storagePersistent.clear();
logThis('Persistent database cleared.', 3);
return true;
}
catch(e) {
logThis('Error while clearing persistent database: ' + e, 1);
}
return false;
}
// Persistent: used to flush the database
function flushPersistent() {
try {
// Get the stored session entry
var session = getPersistent('global', 'session', 1);
// Reset the persistent database
resetPersistent();
// Restaure the stored session entry
if(session)
setPersistent('global', 'session', 1, session);
logThis('Persistent database flushed.', 3);
return true;
}
catch(e) {
logThis('Error while flushing persistent database: ' + e, 1);
}
return false;
}
/* BROWSER DETECT
* http://www.quirksmode.org/js/detect.html
* License: dual-licensed under MPLv2 and the original license
*/
var BrowserDetect = {
init: function () {
this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
this.version = this.searchVersion(navigator.userAgent)
|| this.searchVersion(navigator.appVersion)
|| "an unknown version";
this.OS = this.searchString(this.dataOS) || "an unknown OS";
},
searchString: function (data) {
for (var i=0;i<data.length;i++) {
var dataString = data[i].string;
var dataProp = data[i].prop;
this.versionSearchString = data[i].versionSearch || data[i].identity;
if (dataString) {
if (dataString.indexOf(data[i].subString) != -1)
return data[i].identity;
}
else if (dataProp)
return data[i].identity;
}
},
searchVersion: function (dataString) {
var index = dataString.indexOf(this.versionSearchString);
if (index == -1) return;
return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
},
dataBrowser: [
{
string: navigator.userAgent,
subString: "Chrome",
identity: "Chrome"
},
{ string: navigator.userAgent,
subString: "OmniWeb",
versionSearch: "OmniWeb/",
identity: "OmniWeb"
},
{
string: navigator.vendor,
subString: "Apple",
identity: "Safari",
versionSearch: "Version"
},
{
prop: window.opera,
identity: "Opera"
},
{
string: navigator.vendor,
subString: "iCab",
identity: "iCab"
},
{
string: navigator.vendor,
subString: "KDE",
identity: "Konqueror"
},
{
string: navigator.userAgent,
subString: "Firefox",
identity: "Firefox"
},
{
string: navigator.vendor,
subString: "Camino",
identity: "Camino"
},
{ // for newer Netscapes (6+)
string: navigator.userAgent,
subString: "Netscape",
identity: "Netscape"
},
{
string: navigator.userAgent,
subString: "MSIE",
identity: "Explorer",
versionSearch: "MSIE"
},
{
string: navigator.userAgent,
subString: "Gecko",
identity: "Mozilla",
versionSearch: "rv"
},
{ // for older Netscapes (4-)
string: navigator.userAgent,
subString: "Mozilla",
identity: "Netscape",
versionSearch: "Mozilla"
}
],
dataOS : [
{
string: navigator.platform,
subString: "Win",
identity: "Windows"
},
{
string: navigator.platform,
subString: "Mac",
identity: "Mac"
},
{
string: navigator.userAgent,
subString: "iPhone",
identity: "iPhone/iPod"
},
{
string: navigator.platform,
subString: "Linux",
identity: "Linux"
}
]
};
BrowserDetect.init();
/*
Jappix - An open social platform
These are the homepage JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, LinkMauve
Last revision: 05/03/13
*/
// Allows the user to switch the difference home page elements
function switchHome(div) {
// Path to
var home = '#home .';
var right = home + 'right ';
var current = right + '.homediv.' + div;
// We switch the div
$(right + '.homediv, ' + right + '.top').hide();
$(right + '.' + div).show();
// We reset the homedivs
$(home + 'homediv:not(.default), ' + home + 'top:not(.default)').remove();
// Get the HTML code to display
var disable_form = '';
var lock_host = '';
var code = '';
// Apply the previous link
switch(div) {
case 'loginer':
case 'anonymouser':
case 'registerer':
if(!exists(right + '.top.sub')) {
// Append the HTML code for previous link
$(right + '.top.default').after('<h1 class="top sub loginer anonymouser registerer">&laquo; <a href="#" class="previous">' + _e("Previous") + '</a></h1>');
// Click event on previous link
$(home + 'top.sub a.previous').click(function() {
return switchHome('default');
});
}
break;
}
// Apply the form
switch(div) {
// Login tool
case 'loginer':
lock_host = disableInput(LOCK_HOST, 'on');
code = '<p>' + printf(_e("Login to your existing XMPP account. You can also use the %s to join a groupchat."), '<a href="#" class="to-anonymous">' + _e("anonymous mode") + '</a>') + '</p>' +
'<form action="#" method="post">' +
'<fieldset>' +
'<legend>' + _e("Required") + '</legend>' +
'<label for="lnick">' + _e("Address") + '</label>' +
'<input type="text" class="nick" id="lnick" pattern="[^@/]+" required="" /><span class="jid">@</span><input type="text" class="server" id="lserver" value="' + HOST_MAIN + '" ' + lock_host + ' pattern="[^@/]+" required="" />' +
'<label for="lpassword">' + _e("Password") + '</label>' +
'<input type="password" class="password" id="lpassword" required="" />' +
'<label for="lremember">' + _e("Remember me") + '</label>' +
'<input type="checkbox" class="remember" id="lremember" />' +
'</fieldset>' +
'<a href="#" class="advanced home-images">' + _e("Advanced") + '</a>' +
'<fieldset class="advanced">' +
'<legend>' + _e("Advanced") + '</legend>' +
'<label for="lresource">' + _e("Resource") + '</label>' +
'<input type="text" class="resource" id="lresource" value="' + JAPPIX_RESOURCE + '" />' +
'<label for="lpriority">' + _e("Priority") + '</label>' +
'<select class="priority" id="lpriority">' +
'<option value="1">' + _e("Low") + '</option>' +
'<option value="10" selected="">' + _e("Medium") + '</option>' +
'<option value="100">' + _e("High") + '</option>' +
'</select>' +
'</fieldset>' +
'<input type="submit" value="' + _e("Here we go!") + '" />' +
'</form>';
break;
// Anonymous login tool
case 'anonymouser':
disable_form = disableInput(ANONYMOUS, 'off');
code = '<p>' + printf(_e("Enter the groupchat you want to join and the nick you want to have. You can also go back to the %s."), '<a href="#" class="to-home">' + _e("login page") + '</a>') + '</p>';
if(LEGAL)
code += '<p>' + printf(_e("By using our service, you accept %s."), '<b><a href="' + encodeQuotes(LEGAL) + '" target="_blank">' + _e("our terms of use") + '</a></b>') + '</p>';
code += '<form action="#" method="post">' +
'<fieldset>' +
'<legend>' + _e("Required") + '</legend>' +
'<label>' + _e("Room") + '</label>' +
'<input type="text" class="room"' + disable_form + ' pattern="[^/]+" required="" />' +
'<label>' + _e("Nickname") + '</label>' +
'<input type="text" class="nick"' + disable_form + ' required="" />' +
'</fieldset>' +
'<input type="submit" value="' + _e("Here we go!") + '"' + disable_form + ' />' +
'</form>' +
'<div class="info report">' +
_e("Share this link with your friends:") + ' <span></span>' +
'</div>';
break;
// Register tool
case 'registerer':
disable_form = disableInput(REGISTRATION, 'off');
if(!disable_form)
lock_host = disableInput(LOCK_HOST, 'on');
code = '<p>' + _e("Register a new XMPP account to join your friends on your own social cloud. That's simple!") + '</p>';
if(LEGAL)
code += '<p>' + printf(_e("By using our service, you accept %s."), '<b><a href="' + encodeQuotes(LEGAL) + '" target="_blank">' + _e("our terms of use") + '</a></b>') + '</p>';
code += '<form action="#" method="post">' +
'<fieldset>' +
'<legend>' + _e("Required") + '</legend>' +
'<label for="rnick">' + _e("Address") + '</label>' +
'<input type="text" class="nick" id="rnick" ' + disable_form + ' pattern="[^@/]+" required="" placeholder="' + _e("Username") + '" /><span class="jid">@</span><input type="text" class="server" id="rserver" value="' + HOST_MAIN + '" ' + disable_form + lock_host + ' pattern="[^@/]+" required="" placeholder="' + _e("Server") + '" />' +
'<label for="rpassword">' + _e("Password") + '</label>' +
'<input type="password" class="password" id="rpassword" ' + disable_form + ' required="" placeholder="' + _e("Enter password") + '" /><input type="password" class="spassword" id="spassword" ' + disable_form + ' required="" placeholder="' + _e("Once again...") + '" />';
if(REGISTER_API == 'on')
code += '<div class="captcha_grp">' +
'<label for="captcha">' + _e("Code") + '</label><input type="text" class="captcha" id="captcha" ' + disable_form + ' maxlength="6" pattern="[a-zA-Z0-9]{6}" required="" placeholder="' + _e("Security code") + '" /><img class="captcha_img" src="./php/captcha.php?id=' + genID() + '" alt="" />' +
'</div>';
code += '</fieldset>' +
'<input type="submit" value="' + _e("Here we go!") + '" ' + disable_form + '/>' +
'</form>';
break;
}
// Form disabled?
if(disable_form)
code += '<div class="info fail">' +
_e("This tool has been disabled!") +
'</div>';
// Create this HTML code
if(code && !exists(current)) {
// Append it!
$(right + '.homediv.default').after('<div class="' + div + ' homediv">' + code + '</div>');
// Create the attached events
switch(div) {
// Login tool
case 'loginer':
$(current + ' a.to-anonymous').click(function() {
return switchHome('anonymouser');
});
$(current + ' a.advanced').click(showAdvanced);
$(current + ' form').submit(loginForm);
break;
// Anonymous login tool
case 'anonymouser':
$(current + ' a.to-home').click(function() {
return switchHome('loginer');
});
$(current + ' form').submit(doAnonymous);
// Keyup event on anonymous join's room input
$(current + ' input.room').keyup(function() {
var value = $(this).val();
var report = current + ' .report';
var span = report + ' span';
if(!value) {
$(report).hide();
$(span).text('');
}
else {
$(report).show();
$(span).text(JAPPIX_LOCATION + '?r=' + value);
}
});
break;
// Register tool
case 'registerer':
// Server input change
$('#home input.server').keyup(function(e) {
if(trim($(this).val()) == HOST_MAIN) {
$('#home .captcha_grp').show();
$('#home input.captcha').removeAttr('disabled');
} else {
$('#home .captcha_grp').hide();
$('#home input.captcha').attr('disabled', true);
}
});
// Register input placeholder
// FIXME: breaks IE compatibility
//$('#home input[placeholder]').placeholder();
// Register form submit
$(current + ' form').submit(registerForm);
break;
}
}
// We focus on the first input
$(document).oneTime(10, function() {
$(right + 'input:visible:first').focus();
});
return false;
}
// Allows the user to display the advanced login options
function showAdvanced() {
// Hide the link
$('#home a.advanced').hide();
// Show the fieldset
$('#home fieldset.advanced').show();
return false;
}
// Reads the login form values
function loginForm() {
// We get the values
var lPath = '#home .loginer ';
var lServer = $(lPath + '.server').val();
var lNick = nodeprep($(lPath + '.nick').val());
var lPass = $(lPath + '.password').val();
var lResource = $(lPath + '.resource').val();
var lPriority = $(lPath + '.priority').val();
var lRemember = $(lPath + '.remember').filter(':checked').size();
// Enough values?
if(lServer && lNick && lPass && lResource && lPriority) {
doLogin(lNick, lServer, lPass, lResource, lPriority, lRemember);
} else {
$(lPath + 'input[type="text"], ' + lPath + 'input[type="password"]').each(function() {
var select = $(this);
if(!select.val())
$(document).oneTime(10, function() {
select.addClass('please-complete').focus();
});
else
select.removeClass('please-complete');
});
}
return false;
}
// Reads the register form values
function registerForm() {
var rPath = '#home .registerer ';
// Remove the success info
$(rPath + '.success').remove();
// Get the values
var username = nodeprep($(rPath + '.nick').val());
var domain = $(rPath + '.server').val();
var pass = $(rPath + '.password').val();
var spass = $(rPath + '.spassword').val();
var captcha = $(rPath + '.captcha').val();
// Enough values?
if(domain && username && pass && spass && (pass == spass) && !((REGISTER_API == 'on') && (domain == HOST_MAIN) && !captcha)) {
// We remove the not completed class to avoid problems
$('#home .registerer input').removeClass('please-complete');
// Fire the register event!
doRegister(username, domain, pass, captcha);
}
// Something is missing?
else {
$(rPath + 'input[type="text"], ' + rPath + 'input[type="password"]').each(function() {
var select = $(this);
if(!select.val() || (select.is('#spassword') && pass && (pass != spass)))
$(document).oneTime(10, function() {
select.addClass('please-complete').focus();
});
else
select.removeClass('please-complete');
});
}
return false;
}
// Plugin launcher
function launchHome() {
// Define the vars
var home = '#home ';
var button = home + 'button';
var corp = home + '.corporation';
var aboutus = home + '.aboutus';
var locale = home + '.locale';
// Removes the <noscript /> elements to lighten the DOM
$('noscript').remove();
// Allows the user to switch the home page
$(button).click(function() {
// Login button
if($(this).is('.login'))
return switchHome('loginer');
// Register button
else
return switchHome('registerer');
});
// Allows the user to view the corporation & about infobox
$(corp + ', ' + aboutus).hover(function() {
$(this).addClass('hovered');
}, function() {
$(this).removeClass('hovered');
});
// Allows the user to switch the language
$(locale).hover(function() {
// Initialize the HTML code
var keepget = $(locale).attr('data-keepget');
var html = '<div class="list">';
// Generate each locale HTML code
for(i in LOCALES_AVAILABLE_ID)
html += '<a href="./?l=' + LOCALES_AVAILABLE_ID[i] + keepget + '">' + LOCALES_AVAILABLE_NAMES[i].htmlEnc() + '</a>';
html += '</div>';
// Append the HTML code
$(locale).append(html);
}, function() {
$(locale + ' .list').remove();
});
// Disables the browser HTTP-requests stopper
$(document).keydown(function(e) {
if((e.keyCode == 27) && !isDeveloper())
return false;
});
// Warns for an obsolete browser
if(isObsolete()) {
// Add the code
$(locale).after(
'<div class="obsolete">' +
'<p>' + _e("Your browser is out of date!") + '</p>' +
'<a class="firefox browsers-images" title="' + printf(_e("Last %s version is better!"), 'Mozilla Firefox') + '" href="http://www.mozilla.com/firefox/"></a>' +
'<a class="chrome browsers-images" title="' + printf(_e("Last %s version is better!"), 'Google Chrome') + '" href="http://www.google.com/chrome"></a>' +
'<a class="safari browsers-images" title="' + printf(_e("Last %s version is better!"), 'Safari') + '" href="http://www.apple.com/safari/"></a>' +
'<a class="opera browsers-images" title="' + printf(_e("Last %s version is better!"), 'Opera') + '" href="http://www.opera.com/"></a>' +
'<a class="ie browsers-images" title="' + printf(_e("Last %s version is better!"), 'Internet Explorer') + '" href="http://www.microsoft.com/hk/windows/internet-explorer/"></a>' +
'</div>'
);
// Display it later
$(home + '.obsolete').oneTime('1s', function() {
$(this).slideDown();
});
logThis('Jappix does not support this browser!', 2);
}
logThis('Welcome to Jappix! Happy coding in developer mode!');
}
// Launch this plugin!
$(document).ready(launchHome);
/*
Jappix - An open social platform
These are the talkpage JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 06/05/11
*/
// Creates the talkpage events
function eventsTalkPage() {
// Launch all associated plugins
launchMicroblog();
launchRoster();
launchPresence();
launchPEP();
launchNotifications();
launchMusic();
}
// Creates the talkpage code
function createTalkPage() {
// Talkpage exists?
if(exists('#talk'))
return false;
// Anonymous detector
var anonymous = isAnonymous();
// Generate the HTML code
var html =
'<div id="talk" class="removable">' +
'<div id="top-content">' +
'<div class="tools tools-logo talk-images"></div>' +
'<div class="tools tools-all">';
if(!anonymous) html +=
'<a href="#" onclick="return openInbox();" class="inbox-hidable">' + _e("Messages") + '</a>' +
'<a href="#" onclick="return openVCard();" class="vcard">' + _e("Profile") + '</a>' +
'<a href="#" onclick="return optionsOpen();" class="options-hidable">' + _e("Options") + '</a>' +
'<a href="#" onclick="return normalQuit();" class="quit">' + _e("Disconnect") + '</a>';
else html +=
'<a href="./">' + _e("Disconnect") + '</a>';
html +=
'</div>';
if(!anonymous && document.createElement('audio').canPlayType) html +=
'<div class="tools-all ibubble">' +
'<div class="tools music talk-images" onclick="return openMusic();"></div>' +
'<div class="music-content tools-content bubble hidable">' +
'<div class="tools-content-subarrow talk-images"></div>' +
'<div class="tools-content-subitem">' +
'<div class="player">' +
'<a href="#" class="stop talk-images" onclick="return actionMusic(\'stop\');"></a>' +
'</div>' +
'<div class="list">' +
'<p class="no-results">' + _e("No result!") + '</p>' +
'</div>' +
'<div class="search">' +
'<input type="text" />' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
if(!anonymous) html +=
'<div class="tools-all ibubble">' +
'<div class="tools notifications talk-images" onclick="return showBubble(\'.notifications-content\');"></div>' +
'<div class="notifications-content tools-content bubble hidable">' +
'<div class="tools-content-subarrow talk-images"></div>' +
'<div class="tools-content-subitem">' +
'<a class="empty" href="#" onclick="return clearNotifications();">' + _e("Empty") + '</a>' +
'<p class="nothing">' + _e("No notifications.") + '</p>' +
'</div>' +
'</div>' +
'</div>';
html +=
'</div>' +
'<div id="main-content">' +
'<div id="left-content">';
if(!anonymous) html +=
'<div id="buddy-list">' +
'<div class="content"></div>' +
'<div class="filter">' +
'<input type="text" placeholder="' + _e("Filter") + '" />' +
'<a href="#">x</a>' +
'</div>' +
'<div class="foot ibubble">' +
'<div class="buddy-list-add buddy-list-icon">' +
'<a href="#" class="add talk-images" title="' + _e("Add a friend") + '"></a>' +
'</div>' +
'<div class="buddy-list-join buddy-list-icon">' +
'<a href="#" class="join talk-images" title="' + _e("Join a chat") + '"></a>' +
'</div>' +
'<div class="buddy-list-groupchat buddy-list-icon">' +
'<a href="#" class="groupchat talk-images" title="' + _e("Your groupchats") + '"></a>' +
'</div>' +
'<div class="buddy-list-more buddy-list-icon">' +
'<a href="#" class="more talk-images" title="' + _e("More stuff") + '"></a>' +
'</div>' +
'<div style="clear: both;"></div>' +
'</div>' +
'</div>';
html +=
'<div id="my-infos">' +
'<div class="content">' +
'<div class="element f-presence ibubble">' +
'<a href="#" class="icon picker disabled" data-value="available">' +
'<span class="talk-images"></span>' +
'</a>' +
'<input id="presence-status" type="text" placeholder="' + _e("Status") + '" disabled="" />' +
'</div>';
if(!anonymous) html +=
'<div class="element f-mood pep-hidable ibubble">' +
'<a href="#" class="icon picker" data-value="happy">' +
'<span class="talk-images"></span>' +
'</a>' +
'<input id="mood-text" type="text" placeholder="' + _e("Mood") + '" />' +
'</div>' +
'<div class="element f-activity pep-hidable ibubble">' +
'<a href="#" class="icon picker" data-value="exercising">' +
'<span class="talk-images activity-exercising"></span>' +
'</a>' +
'<input id="activity-text" type="text" placeholder="' + _e("Activity") + '" />' +
'</div>';
html +=
'</div>' +
'</div>' +
'</div>' +
'<div id="right-content">' +
'<div id="page-switch">' +
'<div class="chans">';
if(!anonymous) html +=
'<div class="channel switcher activechan" onclick="return switchChan(\'channel\');">' +
'<div class="icon talk-images"></div>' +
'<div class="name">' + _e("Channel") + '</div>' +
'</div>';
html +=
'</div>' +
'<div class="more ibubble">' +
'<div class="more-button talk-images" onclick="return loadChatSwitch();" title="' + _e("All tabs") + '"></div>' +
'</div>' +
'</div>' +
'<div id="page-engine">';
if(!anonymous) html +=
'<div id="channel" class="page-engine-chan" style="display: block;">' +
'<div class="top mixed ' + hex_md5(getXID()) + '">' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'<div class="update">' +
'<p>' + _e("What\'s up with you?") + '</p>' +
'<div class="microblog-body">' +
'<input class="focusable" type="text" name="microblog_body" placeholder="' + _e("Type something you want to share with your friends...") + '" disabled="" />' +
'</div>' +
'<div class="one-microblog-icon ibubble">' +
'<a href="#" onclick="return showBubble(\'#attach\');" title="' + _e("Attach a file") + '" class="postit attach talk-images"></a>' +
'<form id="attach" class="bubble hidable" action="./php/file-share.php" method="post" enctype="multipart/form-data">' +
'<div class="attach-subarrow talk-images"></div>' +
'<div class="attach-subitem">' +
'<p class="attach-p">' + _e("Attach a file") + '</p>' +
generateFileShare() +
'</div>' +
'</form>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="content mixed"></div>' +
'<div class="footer">' +
'<div class="sync talk-images">' + _e("You are synchronized with your network.") + '</div>' +
'<div class="unsync talk-images">' + _e("Cannot send anything: you can only receive notices!") + '</div>' +
'<div class="fetch wait-small">' + _e("Fetching the social channel...") + '</div>' +
'</div>' +
'</div>';
html +=
'</div>' +
'</div>' +
'</div>' +
'</div>';
// Create the HTML code
$('body').prepend(html);
// Adapt the buddy-list size
adaptRoster();
// Create JS events
eventsTalkPage();
// Start the auto idle functions
liveIdle();
return true;
}
// Destroys the talkpage code
function destroyTalkPage() {
// Reset our database
resetDB();
// Reset some vars
STANZA_ID = 1;
BLIST_ALL = false;
FIRST_PRESENCE_SENT = false;
SEARCH_FILTERED = false;
AVATAR_PENDING = [];
JOIN_SUGGEST = [];
// Kill all timers, exept the board ones
$('*:not(#board .one-board)').stopTime();
// Kill the auto idle functions
dieIdle();
// We renitalise the html markup as its initiale look
$('.removable').remove();
pageTitle('home');
// Finally we show the homepage
$('#home').show();
}
/*
Jappix - An open social platform
These are the popup JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 10/04/12
*/
// Creates a popup code
function createPopup(id, content) {
// Popup exists?
if(exists('#' + id))
return false;
// Popop on top of another one?
var top_of = exists('div.lock:has(div.popup)');
// Append the popup code
$('body').append(
'<div id="' + id + '" class="lock removable">' +
'<div class="popup">' +
content +
'</div>' +
'</div>'
);
// Avoids darker popup background (if on top of another popup)
if(top_of)
$('#' + id).css('background', 'transparent');
// Attach popup events
launchPopup(id);
return true;
}
// Destroys a popup code
function destroyPopup(id) {
// Stop the popup timers
$('#' + id + ' *').stopTime();
// Remove the popup
$('#' + id).remove();
// Manage input focus
inputFocus();
}
// Attaches popup events
function launchPopup(id) {
// Click events
$('#' + id).click(function(evt) {
// Click on lock background?
if($(evt.target).is('.lock:not(.unavoidable)')) {
// Destroy the popup
destroyPopup(id);
return false;
}
});
}
/*
Jappix - An open social platform
These are the audio JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 04/05/12
*/
// Plays the given sound ID
function soundPlay(num) {
try {
// Not supported!
if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9))
return false;
// If the sounds are enabled
if(getDB('options', 'sounds') == '1') {
// If the audio elements aren't yet in the DOM
if(!exists('#audio')) {
$('body').append(
'<div id="audio">' +
'<audio id="new-chat" preload="auto">' +
'<source src="./snd/new-chat.mp3" />' +
'<source src="./snd/new-chat.oga" />' +
'</audio>' +
'<audio id="receive-message" preload="auto">' +
'<source src="./snd/receive-message.mp3" />' +
'<source src="./snd/receive-message.oga" />' +
'</audio>' +
'<audio id="notification" preload="auto">' +
'<source src="./snd/notification.mp3" />' +
'<source src="./snd/notification.oga" />' +
'</audio>' +
'</div>'
);
}
// We play the target sound
var playThis = document.getElementById('audio').getElementsByTagName('audio')[num];
// Fixes Chrome audio bug when Get API serves expired files (for development work purposes)
if(window.chrome && isDeveloper())
playThis.load();
playThis.play();
}
}
catch(e) {}
finally {
return false;
}
}
/*
Jappix - An open social platform
These are the notification board JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou, Maranda
Last revision: 20/02/13
*/
// Creates a board panel
function createBoard(type, id) {
// Text var
var text = '';
// Info
if(type == 'info') {
switch(id) {
// Password change
case 1:
text = _e("Your password has been changed, now you can connect to your account with your new login data.");
break;
// Account deletion
case 2:
text = _e("Your XMPP account has been removed, bye!");
break;
// Account logout
case 3:
text = _e("You have been logged out of your XMPP account, have a nice day!");
break;
// Groupchat join
case 4:
text = _e("The room you tried to join doesn't seem to exist.");
break;
// Groupchat removal
case 5:
text = _e("The groupchat has been removed.");
break;
// Non-existant groupchat user
case 6:
text = _e("The user that you want to reach is not present in the room.");
break;
}
}
// Error
else {
switch(id) {
// Custom error
case 1:
text = '<b>' + _e("Error") + '</b> &raquo; <span></span>';
break;
// Network error
case 2:
text = _e("Jappix has been interrupted by a network issue, a bug or bad login (check that you entered the right credentials), sorry for the inconvenience.");
break;
// List retrieving error
case 3:
text = _e("The element list on this server could not be obtained!");
break;
// Attaching error
case 4:
text = printf(_e("An error occured while uploading your file: maybe it is too big (%s maximum) or forbidden!"), JAPPIX_MAX_UPLOAD);
break;
}
}
// No text?
if(!text)
return false;
// Append the content
$('#board').append('<div class="one-board ' + type + '" data-id="' + id + '">' + text + '</div>');
// Events (click and auto-hide)
$('#board .one-board.' + type + '[data-id="' + id + '"]')
.click(function() {
closeThisBoard(this);
})
.oneTime('5s', function() {
closeThisBoard(this);
})
.slideDown();
return true;
}
// Destroys the existing board notifications
function destroyBoard() {
$('#board').empty();
}
// Executes a given action on the notification board
function actionBoard(id, type) {
// In a first, we destroy other boards
destroyBoard();
// Then we display the board
createBoard(type, id);
}
// Opens a given error ID
function openThisError(id) {
actionBoard(id, 'error');
}
// Opens a given info ID
function openThisInfo(id) {
actionBoard(id, 'info');
}
// Closes a given board
function closeThisBoard(board) {
$(board).slideUp('normal', function() {
$(this).remove();
});
}
// Creates a quick board (HTML5 notification)
function quickBoard(xid, type, content, title, icon) {
// Cannot process?
if(isFocused() || !content || !window.webkitNotifications)
return;
// Default icon?
if(!icon) {
icon = './img/others/default-avatar.png';
// Avatar icon?
if(xid) {
var avatar_xml = XMLFromString(getPersistent('global', 'avatar', xid));
var avatar_type = $(avatar_xml).find('type').text() || 'image/png';
var avatar_binval = $(avatar_xml).find('binval').text();
if(avatar_binval && avatar_type)
icon = 'data:' + avatar_type + ';base64,' + avatar_binval;
}
}
// Default title?
if(!title)
title = _e("New event!");
// Check for notification permission
if(window.webkitNotifications.checkPermission() == 0) {
// Create notification
var notification = window.webkitNotifications.createNotification(icon, title, content);
// Auto-hide after a while
notification.ondisplay = function(event) {
setTimeout(function() {
event.currentTarget.cancel();
}, 10000);
};
// Click event
notification.onclick = function() {
// Click action?
switch(type) {
case 'chat':
switchChan(hex_md5(xid));
break;
case 'groupchat':
switchChan(hex_md5(bareXID(xid)));
break;
default:
break;
}
// Focus on msg-me
window.focus();
// Remove notification
this.cancel();
};
// Show notification
notification.show();
return notification;
}
}
// Asks for permission to show quick boards (HTML5 notification)
function quickBoardPermission() {
if(!window.webkitNotifications || (window.webkitNotifications.checkPermission() == 0))
return;
// Ask for permission
window.webkitNotifications.requestPermission();
}
// Fires quickBoardPermission() on document click
$(document).click(function() {
// Ask for permission to use quick boards
if((typeof con != 'undefined') && con.connected())
quickBoardPermission();
});
/*
Jappix - An open social platform
These are the bubble JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 11/12/10
*/
// Closes all the opened bubbles
function closeBubbles() {
// Destroy all the elements
$('.bubble.hidable:visible').hide();
$('.bubble.removable').remove();
$('body').off('click');
return false;
}
// Click function when a bubble is opened
function showBubble(selector) {
// Hidable bubbles special things
if($(selector).is('.hidable')) {
// This bubble is yet displayed? So abort!
if($(selector).is(':visible'))
return closeBubbles();
// Close all the bubbles
closeBubbles();
// Show the requested bubble
$(selector).show();
}
// Removable bubbles special things
else {
// This bubble is yet added? So abort!
if(exists(selector))
return closeBubbles();
// Close all the bubbles
closeBubbles();
}
// Creates a new click event to close the bubble
$('body').on('click', function(evt) {
var target = evt.target;
// If this is a click away from a bubble
if(!$(target).parents('.ibubble').size())
closeBubbles();
});
return false;
}
/*
Jappix - An open social platform
These are the chat JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Eric, Maranda
Last revision: 20/02/13
*/
// Correctly opens a new chat
function checkChatCreate(xid, type, nickname, password, title) {
// No XID?
if(!xid)
return false;
// We generate some stuffs
var hash = hex_md5(xid);
var name;
// Gets the name of the user/title of the room
if(title)
name = title;
else {
// Private groupchat chat
if(type == 'private')
name = thisResource(xid);
// XMPP-ID
else if(xid.indexOf('@') != -1)
name = getBuddyName(xid);
// Gateway
else
name = xid;
}
// If the target div does not exist
if(!exists('#' + hash)) {
// We check the type of the chat to open
if((type == 'chat') || (type == 'private'))
chatCreate(hash, xid, name, type);
else if(type == 'groupchat') {
// Try to read the room stored configuration
if(!isAnonymous() && (!nickname || !password || !title)) {
// Catch the room data
var fData = $(XMLFromString(getDB('favorites', xid)));
var fNick = fData.find('nick').text();
var fPwd = fData.find('password').text();
var fName = fData.find('name').text();
// Apply the room data
if(!nickname && fNick)
nickname = fNick;
if(!password && fPwd)
password = fPwd;
if(!title && fName)
name = fName;
}
groupchatCreate(hash, xid, name, nickname, password);
}
}
// Switch to the newly-created chat
switchChan(hash);
return false;
}
// Generates the chat DOM elements
function generateChat(type, id, xid, nick) {
// Generate some stuffs
var path = '#' + id + ' .';
var escaped_xid = escape(xid);
// Special code
var specialAttributes, specialAvatar, specialName, specialCode, specialLink, specialDisabled, specialStyle, specialMAM;
// Groupchat special code
if(type == 'groupchat') {
specialAttributes = ' data-type="groupchat"';
specialAvatar = '';
specialName = '<p class="bc-infos"><b>' + _e("Subject") + '</b> <span class="muc-topic">' + _e("no subject defined for this room.") + '</span></p>';
specialCode = '<div class="content groupchat-content" id="chat-content-' + id + '"></div><div class="list"><div class="moderator role"><p class="title">' + _e("Moderators") + '</p></div><div class="participant role"><p class="title">' + _e("Participants") + '</p></div><div class="visitor role"><p class="title">' + _e("Visitors") + '</p></div><div class="none role"><p class="title">' + _e("Others") + '</p></div></div>';
specialLink = '<a href="#" class="tools-mucadmin tools-tooltip talk-images chat-tools-content" title="' + _e("Administration panel for this room") + '"></a>';
specialStyle = '';
// Is this a gateway?
if(xid.match(/%/))
specialDisabled = '';
else
specialDisabled = ' disabled=""';
}
// Chat (or other things?!) special code
else {
specialMAM = '<div class="wait-mam wait-small"></div>';
specialAttributes = ' data-type="chat"';
specialAvatar = '<div class="avatar-container"><img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" /></div>';
specialName = '<div class="bc-pep"></div><p class="bc-infos"><span class="unavailable show talk-images"></span></p>';
specialCode = '<div class="content" id="chat-content-' + id + '">' + specialMAM + '</div>';
specialLink = '<a href="#" class="tools-infos tools-tooltip talk-images chat-tools-content" title="' + _e("Show user profile") + '"></a>';
specialStyle = ' style="display: none;"';
specialDisabled = '';
}
// Not a groupchat private chat, we can use the buddy add icon
if((type == 'chat') || (type == 'groupchat')) {
var addTitle;
if(type == 'chat')
addTitle = _e("Add this contact to your friends");
else
addTitle = _e("Add this groupchat to your favorites");
specialLink += '<a href="#" class="tools-add tools-tooltip talk-images chat-tools-content" title="' + addTitle + '"></a>';
}
// IE DOM parsing bug fix
var specialStylePicker = '<div class="chat-tools-content chat-tools-style"' + specialStyle + '>' +
'<a href="#" class="tools-style tools-tooltip talk-images"></a>' +
'</div>';
if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9))
specialStylePicker = '';
// Append the chat HTML code
$('#page-engine').append(
'<div id="' + id + '" class="page-engine-chan chat one-counter"' + specialAttributes + ' data-xid="' + escaped_xid + '">' +
'<div class="top ' + id + '">' +
specialAvatar +
'<div class="name">' +
'<p class="bc-name bc-name-nick">' + nick.htmlEnc() + '</p>' +
specialName +
'</div>' +
'</div>' +
specialCode +
'<div class="text">' +
'<div class="footer">' +
'<div class="chat-tools-content chat-tools-smileys">' +
'<a href="#" class="tools-smileys tools-tooltip talk-images"></a>' +
'</div>' +
specialStylePicker +
'<div class="chat-tools-content chat-tools-file">' +
'<a href="#" class="tools-file tools-tooltip talk-images"></a>' +
'</div>' +
'<div class="chat-tools-content chat-tools-save">' +
'<a href="#" class="tools-save tools-tooltip talk-images"></a>' +
'</div>' +
'<a href="#" class="tools-clear tools-tooltip talk-images chat-tools-content" title="' + _e("Clean current chat") + '"></a>' +
specialLink +
'</div>' +
'<div class="compose">' +
'<textarea class="message-area focusable" ' + specialDisabled + ' data-to="' + escaped_xid + '" /></textarea>' +
'</div>' +
'</div>' +
'</div>'
);
// Click event: chat cleaner
$(path + 'tools-clear').click(function() {
cleanChat(id);
});
// Click event: user-infos
$(path + 'tools-infos').click(function() {
openUserInfos(xid);
});
}
// Generates the chat switch elements
function generateSwitch(type, id, xid, nick) {
// Path to the element
var chat_switch = '#page-switch .';
// Special code
var specialClass = ' unavailable';
var show_close = true;
// Groupchat
if(type == 'groupchat') {
specialClass = ' groupchat-default';
if(isAnonymous() && (xid == generateXID(ANONYMOUS_ROOM, 'groupchat')))
show_close = false;
}
// Generate the HTML code
var html = '<div class="' + id + ' switcher chan" onclick="return switchChan(\'' + encodeOnclick(id) + '\')">' +
'<div class="icon talk-images' + specialClass + '"></div>' +
'<div class="name">' + nick.htmlEnc() + '</div>';
// Show the close button if not MUC and not anonymous
if(show_close)
html += '<div class="exit" title="' + _e("Close this tab") + '" onclick="return quitThisChat(\'' + encodeOnclick(xid) + '\', \'' + encodeOnclick(id) + '\', \'' + encodeOnclick(type) + '\');">x</div>';
// Close the HTML
html += '</div>';
// Append the HTML code
$(chat_switch + 'chans, ' + chat_switch + 'more-content').append(html);
}
// Cleans given the chat lines
function cleanChat(chat) {
// Remove the messages
$('#page-engine #' + chat + ' .content .one-group').remove();
// Clear the history database
removePersistent(getXID(), 'history', chat);
// Focus again
$(document).oneTime(10, function() {
$('#page-engine #' + chat + ' .text .message-area').focus();
});
}
// Creates a new chat
function chatCreate(hash, xid, nick, type) {
logThis('New chat: ' + xid, 3);
// Create the chat content
generateChat(type, hash, xid, nick);
// Create the chat switcher
generateSwitch(type, hash, xid, nick);
// If the user is not in our buddy-list
if(type == 'chat') {
// MAM? Get archives from there!
if(enabledMAM()) {
getArchivesMAM({
'with': xid
}, {
'max': MAM_REQ_MAX,
'before': ''
});
} else {
// Restore the chat history
var chat_history = getPersistent(getXID(), 'history', hash);
if(chat_history) {
// Generate hashs
var my_hash = hex_md5(getXID());
var friend_hash = hex_md5(xid);
// Add chat history HTML
$('#' + hash + ' .content').append(chat_history);
// Filter old groups & messages
$('#' + hash + ' .one-group[data-type="user-message"]').addClass('from-history').attr('data-type', 'old-message');
$('#' + hash + ' .user-message').removeClass('user-message').addClass('old-message');
// Regenerate user names
$('#' + hash + ' .one-group.' + my_hash + ' b.name').text(getBuddyName(getXID()));
$('#' + hash + ' .one-group.' + friend_hash + ' b.name').text(getBuddyName(xid));
// Regenerate group dates
$('#' + hash + ' .one-group').each(function() {
var current_stamp = parseInt($(this).attr('data-stamp'));
$(this).find('span.date').text(relativeDate(current_stamp));
});
// Regenerate avatars
if(exists('#' + hash + ' .one-group.' + my_hash + ' .avatar-container'))
getAvatar(getXID(), 'cache', 'true', 'forget');
if(exists('#' + hash + ' .one-group.' + friend_hash + ' .avatar-container'))
getAvatar(xid, 'cache', 'true', 'forget');
}
}
// Add button
if(!exists('#buddy-list .buddy[data-xid="' + escape(xid) + '"]'))
$('#' + hash + ' .tools-add').click(function() {
// Hide the icon (to tell the user all is okay)
$(this).hide();
// Send the subscribe request
addThisContact(xid, nick);
}).show();
}
// We catch the user's informations (like this avatar, vcard, and so on...)
getUserInfos(hash, xid, nick, type);
// The icons-hover functions
tooltipIcons(xid, hash);
// The event handlers
var inputDetect = $('#page-engine #' + hash + ' .message-area');
inputDetect.focus(function() {
// Clean notifications for this chat
chanCleanNotify(hash);
// Store focus on this chat!
CHAT_FOCUS_HASH = hash;
});
inputDetect.blur(function() {
// Reset storage about focus on this chat!
if(CHAT_FOCUS_HASH == hash)
CHAT_FOCUS_HASH = null;
});
inputDetect.keypress(function(e) {
// Enter key
if(e.keyCode == 13) {
// Add a new line
if(e.shiftKey)
inputDetect.val(inputDetect.val() + '\n');
// Send the message
else {
// Send the message
sendMessage(hash, 'chat');
// Reset the composing database entry
setDB('chatstate', xid, 'off');
}
return false;
}
});
// Scroll in chat content
$('#page-engine #' + hash + ' .content').scroll(function() {
if(enabledMAM() && !(xid in MAM_MAP_PENDING)) {
var has_state = xid in MAM_MAP_STATES;
var rsm_count = has_state ? MAM_MAP_STATES[xid]['rsm']['count'] : 1;
var rsm_before = has_state ? MAM_MAP_STATES[xid]['rsm']['first'] : '';
// Request more archives?
if(rsm_count > 0 && $(this).scrollTop() < 200) {
var wait_mam = $('#' + hash).find('.wait-mam');
wait_mam.show();
getArchivesMAM({
'with': xid
}, {
'max': MAM_REQ_MAX,
'before': rsm_before
}, function() {
wait_mam.hide();
});
}
}
});
// Chatstate events
eventsChatState(inputDetect, xid, hash, 'chat');
}
/*
Jappix - An open social platform
These are the groupchat JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda, Eric
Last revision: 13/02/12
*/
// Displays the MUC admin elements
function displayMucAdmin(affiliation, id, xid, statuscode) {
// We must be in the "login" mode
if(isAnonymous())
return;
// We check if the user is a room owner or administrator to give him privileges
if(affiliation == 'owner' || affiliation == 'admin')
$('#' + id + ' .tools-mucadmin').show();
// We check if the room hasn't been yet created
if(statuscode == 201)
openThisInfo(4);
// We add the click event
$('#' + id + ' .tools-mucadmin').click(function() {
openMucAdmin(xid, affiliation);
});
}
// Initializes a connection with a MUC groupchat
function getMUC(room, nickname, password) {
// Room hash
var hash = hex_md5(room);
// Reset the elements
$('#' + hash + ' .muc-ask').remove();
$('#' + hash + ' .compose').show();
// No nickname?
if(!nickname) {
// Get some values
if(!isAnonymous())
nickname = getNick();
else
nickname = ANONYMOUS_NICK;
// If the nickname could not be retrieved, ask it
if(!nickname)
generateMUCAsk('nickname', room, hash, nickname, password);
}
// Got our nickname?
if(nickname) {
// Get our general presence
var show = getDB('presence-show', 1);
var status = getDB('options', 'presence-status');
// Set my nick
$('#' + hash).attr('data-nick', escape(nickname));
// Send the appropriate presence
sendPresence(room + '/' + nickname, '', show, status, '', true, password, handleMUC);
}
return false;
}
// Handles the MUC main elements
function handleMUC(presence) {
// We get the xml content
var xml = presence.getNode();
var from = fullXID(getStanzaFrom(presence));
var room = bareXID(from);
var nickname = thisResource(from);
var hash = hex_md5(room);
// No ID: must fix M-Link bug
if(presence.getID() == null)
presence.setID(1);
logThis('First MUC presence: ' + from, 3);
// Catch the errors
if(!handleError(xml)) {
// Define some stuffs
var muc_user = $(xml).find('x[xmlns="' + NS_MUC_USER + '"]');
var affiliation = muc_user.find('item').attr('affiliation');
var statuscode = parseInt(muc_user.find('status').attr('code'));
// Handle my presence
handlePresence(presence);
// Check if I am a room owner
displayMucAdmin(affiliation, hash, room, statuscode);
// Tell the MUC we can notify the incoming presences
$(document).oneTime('15s', function() {
$('#' + hash).attr('data-initial', 'true');
});
// Enable the chatting input
$(document).oneTime(10, function() {
$('#' + hash + ' .message-area').removeAttr('disabled').focus();
});
}
// A password is required
else if($(xml).find('error[type="auth"] not-authorized').size())
generateMUCAsk('password', room, hash, nickname);
// There's a nickname conflict
else if($(xml).find('error[type="cancel"] conflict').size())
generateMUCAsk('nickname', room, hash);
}
// Generates a correct MUC asker
function generateMUCAsk(type, room, hash, nickname, password) {
// Generate the path to the elements
var path_to = '#' + hash + ' .muc-ask';
// Define the label text
var label_text;
switch(type) {
case 'nickname':
label_text = _e("Nickname");
break;
case 'password':
label_text = _e("Password");
break;
}
// Create the HTML markup
$('#' + hash + ' .compose').hide();
$('#' + hash).append(
'<div class="muc-ask text">' +
'<label>' + label_text + '</label>' +
'<input class="focusable" type="text" />' +
'</div>'
);
// When a key is pressed in the input
$(path_to + ' input').keyup(function(e) {
var value_input = $(this).val();
// Enter key pressed
if(e.keyCode == 13) {
// trim() fixes #304
if(type == 'nickname' && trim(value_input)) {
nickname = trim(value_input);
return getMUC(room, nickname, password);
}
if(type == 'password' && value_input) {
password = value_input;
return getMUC(room, nickname, password);
}
}
});
// Focus on the input
$(document).oneTime(10, function() {
$(path_to + ' input').focus();
});
}
// Creates a new groupchat
function groupchatCreate(hash, room, chan, nickname, password) {
/* REF: http://xmpp.org/extensions/xep-0045.html */
logThis('New groupchat: ' + room, 3);
// Create the chat content
generateChat('groupchat', hash, room, chan);
// Create the chat switcher
generateSwitch('groupchat', hash, room, chan);
// The icons-hover functions
tooltipIcons(room, hash);
// Click event on the add tool
$('#' + hash + ' .tools-add').click(function() {
// Hide the icon (to tell the user all is okay)
$(this).hide();
// Add the groupchat to the user favorites
addThisFavorite(room, chan);
});
// Must show the add button?
if(!existDB('favorites', room))
$('#' + hash + ' .tools-add').show();
// The event handlers
var inputDetect = $('#' + hash + ' .message-area');
// Focus event
inputDetect.focus(function() {
// Clean notifications for this chat
chanCleanNotify(hash);
// Store focus on this chat!
CHAT_FOCUS_HASH = hash;
})
// Blur event
inputDetect.blur(function() {
// Reset storage about focus on this chat!
if(CHAT_FOCUS_HASH == hash)
CHAT_FOCUS_HASH = null;
// Reset autocompletion
resetAutocompletion(hash);
})
// Lock to the input
inputDetect.keydown(function(e) {
// Enter key
if(e.keyCode == 13) {
// Add a new line
if(e.shiftKey)
inputDetect.val(inputDetect.val() + '\n');
// Send the message
else {
sendMessage(hash, 'groupchat');
// Reset the composing database entry
setDB('chatstate', room, 'off');
}
return false;
}
// Tabulation key
else if(e.keyCode == 9) {
createAutocompletion(hash);
return false;
}
// Reset the autocompleter
else
resetAutocompletion(hash);
});
// Chatstate events
eventsChatState(inputDetect, room, hash, 'groupchat');
// Get the current muc informations and content
getMUC(room, nickname, password);
}
// Generates a groupchat to join array
function arrayJoinGroupchats() {
// Values array
var muc_arr = [GROUPCHATS_JOIN];
var new_arr = [];
// Try to split it
if(GROUPCHATS_JOIN.indexOf(',') != -1)
muc_arr = GROUPCHATS_JOIN.split(',');
for(i in muc_arr) {
// Get the current value
var muc_current = trim(muc_arr[i]);
// No current value?
if(!muc_current)
continue;
// Filter the current value
muc_current = generateXID(muc_current, 'groupchat');
// Add the current value
if(!existArrayValue(new_arr, muc_current))
new_arr.push(muc_current);
}
return new_arr;
}
// Joins the defined groupchats
var JOIN_SUGGEST = [];
function joinConfGroupchats() {
// Nothing to join?
if(!JOIN_SUGGEST)
return;
// Join the chats
if(JOIN_SUGGEST.length) {
for(g in JOIN_SUGGEST)
checkChatCreate(JOIN_SUGGEST[g], 'groupchat');
}
}
// Checks suggest utility
function suggestCheck() {
var groupchat_arr = arrayJoinGroupchats();
// Must suggest the user?
if((GROUPCHATS_SUGGEST == 'on') && groupchat_arr.length) {
if(exists('#suggest'))
return;
// Create HTML code
var html = '<div id="suggest">';
html += '<div class="title">' + _e("Suggested chatrooms") + '</div>';
html += '<div class="content">';
for(g in groupchat_arr) {
html += '<a class="one" href="#" data-xid="' + encodeQuotes(groupchat_arr[g]) + '">';
html += '<span class="icon talk-images"></span>';
html += '<span class="name">' + capitaliseFirstLetter(getXIDNick(groupchat_arr[g]).htmlEnc()) + '</span>';
html += '<span class="state talk-images"></span>';
html += '<span class="clear"></span>';
html += '</a>';
}
html += '</div>';
html += '<a class="next disabled" href="#">' + _e("Continue") + '</a>';
html += '</div>';
// Append HTML code
$('body').append(html);
// Click events
$('#suggest .content a.one').click(function() {
// Add/remove the active class
$(this).toggleClass('active');
// We require at least one room to be chosen
if(exists('#suggest .content a.one.active'))
$('#suggest a.next').removeClass('disabled');
else
$('#suggest a.next').addClass('disabled');
return false;
});
$('#suggest a.next').click(function() {
// Disabled?
if($(this).hasClass('disabled'))
return false;
// Store groupchats to join
$('#suggest .content a.one.active').each(function() {
JOIN_SUGGEST.push($(this).attr('data-xid'));
});
// Switch to talk UI
$('#suggest').remove();
triggerConnected();
return false;
});
} else {
JOIN_SUGGEST = groupchat_arr;
triggerConnected();
}
}
/*
Jappix - An open social platform
These are the smileys JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 21/03/11
*/
// Generates the correct HTML code for an emoticon insertion tool
function emoteLink(smiley, image, hash) {
return '<a href="#" class="emoticon emoticon-' + image + ' smileys-images" data-smiley="' + smiley + '"></a>';
}
// Emoticon links arrays
function smileyLinks(hash) {
var links = '';
var sArray = new Array(
':-D',
']:->',
'8-)',
':-P',
':-)',
';-)',
':-$',
':-|',
':-/',
'=-O',
':-(',
':\'-(',
':-@',
':-!',
'({)',
'(})',
':3',
'(@)',
':-[',
':-{}',
'<3',
'</3',
'@}->--',
'(W)',
'(Y)',
'(N)',
'(I)',
'(C)',
'(D)',
'(B)',
'(Z)',
'(X)',
'(P)',
'(T)',
'(8)',
'(%)',
'(E)',
'(R)',
'(*)',
'(S)'
);
var cArray = new Array(
'biggrin',
'devil',
'coolglasses',
'tongue',
'smile',
'wink',
'blush',
'stare',
'frowning',
'oh',
'unhappy',
'cry',
'angry',
'puke',
'hugright',
'hugleft',
'lion',
'pussy',
'bat',
'kiss',
'heart',
'brheart',
'flower',
'brflower',
'thumbup',
'thumbdown',
'lamp',
'coffee',
'drink',
'beer',
'boy',
'girl',
'photo',
'phone',
'music',
'cuffs',
'mail',
'rainbow',
'star',
'moon'
);
for(i in sArray)
links += emoteLink(sArray[i], cArray[i], hash);
return links;
}
/*
Jappix - An open social platform
These are the Out of Band Data JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 27/08/11
*/
// Sends an OOB request to someone
function sendOOB(to, type, url, desc) {
// IQ stanza?
if(type == 'iq') {
// Get some values
var id = hex_md5(genID() + to + url + desc);
to = highestPriority(to);
// IQs cannot be sent to offline users
if(!to)
return;
// Register the ID
setDB('send/url', id, url);
setDB('send/desc', id, desc);
var aIQ = new JSJaCIQ();
aIQ.setTo(fullXID(to));
aIQ.setType('set');
aIQ.setID(id);
// Append the query content
var aQuery = aIQ.setQuery(NS_IQOOB);
aQuery.appendChild(aIQ.buildNode('url', {'xmlns': NS_IQOOB}, url));
aQuery.appendChild(aIQ.buildNode('desc', {'xmlns': NS_IQOOB}, desc));
con.send(aIQ);
}
// Message stanza?
else {
var aMsg = new JSJaCMessage();
aMsg.setTo(bareXID(to));
// Append the content
aMsg.setBody(desc);
var aX = aMsg.appendNode('x', {'xmlns': NS_XOOB});
aX.appendChild(aMsg.buildNode('url', {'xmlns': NS_XOOB}, url));
con.send(aMsg);
}
logThis('Sent OOB request to: ' + to + ' (' + desc + ')');
}
// Handles an OOB request
function handleOOB(from, id, type, node) {
var xid = url = desc = '';
// IQ stanza?
if(type == 'iq') {
xid = fullXID(from);
url = $(node).find('url').text();
desc = $(node).find('desc').text();
}
// Message stanza?
else {
xid = bareXID(from);
url = $(node).find('url').text();
desc = $(node).find('body').text();
}
// No desc?
if(!desc)
desc = url;
// Open a new notification
if(type && xid && url && desc)
newNotification('send', xid, [xid, url, type, id, node], desc, hex_md5(xid + url + desc + id));
}
// Replies to an OOB request
function replyOOB(to, id, choice, type, node) {
// Not IQ type?
if(type != 'iq')
return;
// New IQ
var aIQ = new JSJaCIQ();
aIQ.setTo(to);
aIQ.setID(id);
// OOB request accepted
if(choice == 'accept') {
aIQ.setType('result');
logThis('Accepted file request from: ' + to, 3);
}
// OOB request rejected
else {
aIQ.setType('error');
// Append stanza content
for(var i = 0; i < node.childNodes.length; i++)
aIQ.getNode().appendChild(node.childNodes.item(i).cloneNode(true));
// Append error content
var aError = aIQ.appendNode('error', {'xmlns': NS_CLIENT, 'code': '406', 'type': 'modify'});
aError.appendChild(aIQ.buildNode('not-acceptable', {'xmlns': NS_STANZAS}));
logThis('Rejected file request from: ' + to, 3);
}
con.send(aIQ);
}
// Wait event for OOB upload
function waitUploadOOB() {
// Append the wait icon
$('#page-engine .chat-tools-file:not(.mini) .tooltip-subitem *').hide();
$('#page-engine .chat-tools-file:not(.mini) .tooltip-subitem').append('<div class="wait wait-medium"></div>');
// Lock the bubble
$('#page-engine .chat-tools-file:not(.mini)').addClass('mini');
}
// Success event for OOB upload
function handleUploadOOB(responseXML) {
// Data selector
var dData = $(responseXML).find('jappix');
// Get the values
var fID = dData.find('id').text();
var fURL = dData.find('url').text();
var fDesc = dData.find('desc').text();
// Get the OOB values
var oob_has;
// No ID provided?
if(!fID)
oob_has = ':has(.wait)';
else
oob_has = ':has(#oob-upload input[value="' + fID + '"])';
var xid = $('#page-engine .page-engine-chan' + oob_has).attr('data-xid');
var oob_type = $('#page-engine .chat-tools-file' + oob_has).attr('data-oob');
// Reset the file send tool
$('#page-engine .chat-tools-file' + oob_has).removeClass('mini');
$('#page-engine .bubble-file' + oob_has).remove();
// Not available?
if($('#page-engine .chat-tools-file' + oob_has).is(':hidden') && (oob_type == 'iq')) {
openThisError(4);
// Remove the file we sent
if(fURL)
$.get(fURL + '&action=remove');
}
// Everything okay?
else if(fURL && fDesc && !dData.find('error').size()) {
// Send the OOB request
sendOOB(xid, oob_type, fURL, fDesc);
// Notify the sender
newNotification('send_pending', xid, [xid, fURL, oob_type, '', ''], fDesc, hex_md5(fURL + fDesc + fID));
logThis('File request sent.', 3);
}
// Upload error?
else {
openThisError(4);
logThis('Error while sending the file: ' + dData.find('error').text(), 1);
}
}
/*
Jappix - An open social platform
These are the avatar JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou, Maranda
Last revision: 20/02/13
*/
// Requests the avatar of a given user
var AVATAR_PENDING = [];
function getAvatar(xid, mode, enabled, photo) {
/* REF: http://xmpp.org/extensions/xep-0153.html */
// No need to get the avatar, another process is yet running
if(existArrayValue(AVATAR_PENDING, xid))
return false;
// Initialize: XML data is in one SQL entry, because some browser are sloooow with SQL requests
var xml = XMLFromString(getPersistent('global', 'avatar', xid));
var forced = false;
// Retrieving forced?
if($(xml).find('forced').text() == 'true')
forced = true;
// No avatar in presence
if(!photo && !forced && enabled == 'true') {
// Pending marker
AVATAR_PENDING.push(xid);
// Reset the avatar
resetAvatar(xid, hex_md5(xid));
logThis('No avatar for: ' + xid, 2);
}
// Try to catch the avatar
else {
// Define some stuffs
var type = $(xml).find('type').text();
var binval = $(xml).find('binval').text();
var checksum = $(xml).find('checksum').text();
var updated = false;
// Process the checksum of the avatar
if(checksum == photo || photo == 'forget' || forced)
updated = true;
// If the avatar is yet stored and a new retrieving is not needed
if(mode == 'cache' && type && binval && checksum && updated) {
// Pending marker
AVATAR_PENDING.push(xid);
// Display the cache avatar
displayAvatar(xid, hex_md5(xid), type, binval);
logThis('Read avatar from cache: ' + xid, 3);
}
// Else if the request has not yet been fired, we get it
else if((!updated || mode == 'force' || photo == 'forget') && enabled != 'false') {
// Pending marker
AVATAR_PENDING.push(xid);
// Get the latest avatar
var iq = new JSJaCIQ();
iq.setType('get');
iq.setTo(xid);
iq.appendNode('vCard', {'xmlns': NS_VCARD});
con.send(iq, handleAvatar);
logThis('Get avatar from server: ' + xid, 3);
}
}
return true;
}
// Handles the avatar
function handleAvatar(iq) {
// Extract the XML values
var handleXML = iq.getNode();
var handleFrom = fullXID(getStanzaFrom(iq));
// Is this me? Remove the resource!
if(bareXID(handleFrom) == getXID())
handleFrom = bareXID(handleFrom);
// Get some other values
var hash = hex_md5(handleFrom);
var find = $(handleXML).find('vCard');
var aChecksum = 'none';
var oChecksum = null;
// Read our own checksum
if(handleFrom == getXID()) {
oChecksum = getDB('checksum', 1);
// Avoid the "null" value
if(!oChecksum)
oChecksum = '';
}
// vCard not empty?
if(find.size()) {
// We get our profile details
if(handleFrom == getXID()) {
// Get the names
var names = generateBuddyName(iq);
// Write the values to the database
setDB('profile', 'name', names[0]);
setDB('profile', 'nick', names[1]);
}
// We get the avatar
var aType = find.find('TYPE:first').text();
var aBinval = find.find('BINVAL:first').text();
// No binval?
if(!aBinval) {
aType = 'none';
aBinval = 'none';
}
// Enough data
else {
// No type?
if(!aType)
aType = 'image/png';
// Process the checksum
else
aChecksum = hex_sha1(Base64.decode(aBinval));
}
// We display the user avatar
displayAvatar(handleFrom, hash, aType, aBinval);
// Store the avatar
setPersistent('global', 'avatar', handleFrom, '<avatar><type>' + aType + '</type><binval>' + aBinval + '</binval><checksum>' + aChecksum + '</checksum><forced>false</forced></avatar>');
logThis('Avatar retrieved from server: ' + handleFrom, 3);
}
// vCard is empty
else
resetAvatar(handleFrom);
// We got a new checksum for us?
if(((oChecksum != null) && (oChecksum != aChecksum)) || !FIRST_PRESENCE_SENT) {
// Define a proper checksum
var pChecksum = aChecksum;
if(pChecksum == 'none')
pChecksum = '';
// Update our temp. checksum
setDB('checksum', 1, pChecksum);
// Send the stanza
if(!FIRST_PRESENCE_SENT)
getStorage(NS_OPTIONS);
else if(hasPersistent())
presenceSend(pChecksum);
}
}
// Reset the avatar of an user
function resetAvatar(xid, hash) {
// Store the empty avatar
setPersistent('global', 'avatar', xid, '<avatar><type>none</type><binval>none</binval><checksum>none</checksum><forced>false</forced></avatar>');
// Display the empty avatar
displayAvatar(xid, hash, 'none', 'none');
}
// Displays the avatar of an user
function displayAvatar(xid, hash, type, binval) {
// Initialize the vars
var container = hash + ' .avatar-container';
var code = '<img class="avatar" src="';
// If the avatar exists
if((type != 'none') && (binval != 'none'))
code += 'data:' + type + ';base64,' + binval;
else
code += './img/others/default-avatar.png';
code += '" alt="" />';
// Replace with the new avatar (in the roster and in the chat)
$('.' + container).html(code);
// We can remove the pending marker
removeArrayValue(AVATAR_PENDING, xid);
}
/*
Jappix - An open social platform
These are the mucadmin JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda
Last revision: 03/03/11
*/
// Opens the MUC admin popup
function openMucAdmin(xid, aff) {
// Popup HTML content
var html_full =
'<div class="top">' + _e("MUC administration") + '</div>' +
'<div class="content">' +
'<div class="head mucadmin-head">' +
'<div class="head-text mucadmin-head-text">' + _e("You administrate this room") + '</div>' +
'<div class="mucadmin-head-jid">' + xid + '</div>' +
'</div>' +
'<div class="mucadmin-forms">' +
'<div class="mucadmin-topic">' +
'<fieldset>' +
'<legend>' + _e("Subject") + '</legend>' +
'<label for="topic-text">' + _e("Enter new subject") + '</label>' +
'<textarea id="topic-text" name="room-topic" rows="8" cols="60" ></textarea>' +
'</fieldset>' +
'</div>' +
'<div class="mucadmin-conf">' +
'<fieldset>' +
'<legend>' + _e("Configuration") + '</legend>' +
'<div class="results mucadmin-results"></div>' +
'</fieldset>' +
'</div>' +
'<div class="mucadmin-aut">' +
'<fieldset>' +
'<legend>' + _e("Authorizations") + '</legend>' +
'<label>' + _e("Member list") + '</label>' +
'<div class="aut-member aut-group">' +
'<a href="#" class="aut-add" onclick="return addInputMucAdmin(\'\', \'member\');">' + _e("Add an input") + '</a>' +
'</div>' +
'<label>' + _e("Owner list") + '</label>' +
'<div class="aut-owner aut-group">' +
'<a href="#" class="aut-add" onclick="return addInputMucAdmin(\'\', \'owner\');">' + _e("Add an input") + '</a>' +
'</div>' +
'<label>' + _e("Administrator list") + '</label>' +
'<div class="aut-admin aut-group">' +
'<a href="#" class="aut-add" onclick="return addInputMucAdmin(\'\', \'admin\');">' + _e("Add an input") + '</a>' +
'</div>' +
'<label>' + _e("Outcast list") + '</label>' +
'<div class="aut-outcast aut-group">' +
'<a href="#" class="aut-add" onclick="return addInputMucAdmin(\'\', \'outcast\');">' + _e("Add an input") + '</a>' +
'</div>' +
'</fieldset>' +
'</div>' +
'<div class="mucadmin-others">' +
'<fieldset>' +
'<legend>' + _e("Others") + '</legend>' +
'<label>' + _e("Destroy this MUC") + '</label>' +
'<a href="#" onclick="return destroyMucAdmin();">' + _e("Yes, let's do it!") + '</a>' +
'</fieldset>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish save">' + _e("Save") + '</a>' +
'<a href="#" class="finish cancel">' + _e("Cancel") + '</a>' +
'</div>';
var html_partial =
'<div class="top">' + _e("MUC administration") + '</div>' +
'<div class="content">' +
'<div class="head mucadmin-head">' +
'<div class="head-text mucadmin-head-text">' + _e("You administrate this room") + '</div>' +
'<div class="mucadmin-head-jid">' + xid + '</div>' +
'</div>' +
'<div class="mucadmin-forms">' +
'<div class="mucadmin-aut">' +
'<fieldset>' +
'<legend>' + _e("Authorizations") + '</legend>' +
'<label>' + _e("Member list") + '</label>' +
'<div class="aut-member aut-group">' +
'<a href="#" class="aut-add" onclick="return addInputMucAdmin(\'\', \'member\');">' + _e("Add an input") + '</a>' +
'</div>' +
'<label>' + _e("Outcast list") + '</label>' +
'<div class="aut-outcast aut-group">' +
'<a href="#" class="aut-add" onclick="return addInputMucAdmin(\'\', \'outcast\');">' + _e("Add an input") + '</a>' +
'</div>' +
'</fieldset>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish save">' + _e("Save") + '</a>' +
'<a href="#" class="finish cancel">' + _e("Cancel") + '</a>' +
'</div>';
// Create the popup
if(aff == 'owner')
createPopup('mucadmin', html_full);
if(aff == 'admin')
createPopup('mucadmin', html_partial);
// Associate the events
launchMucAdmin();
// We get the affiliated user's privileges
if(aff == 'owner') {
queryMucAdmin(xid, 'member');
queryMucAdmin(xid, 'owner');
queryMucAdmin(xid, 'admin');
queryMucAdmin(xid, 'outcast');
// We query the room to edit
dataForm(xid, 'muc', '', '', 'mucadmin');
} else if(aff == 'admin') {
queryMucAdmin(xid, 'member');
queryMucAdmin(xid, 'outcast');
}
}
// Closes the MUC admin popup
function closeMucAdmin() {
// Destroy the popup
destroyPopup('mucadmin');
return false;
}
// Removes a MUC admin input
function removeInputMucAdmin(element) {
var path = $(element).parent();
// We first hide the container of the input
path.hide();
// Then, we add a special class to the input
path.find('input').addClass('aut-dustbin');
return false;
}
// Adds a MUC admin input
function addInputMucAdmin(xid, affiliation) {
var hash = hex_md5(xid + affiliation);
// Add the HTML code
$('#mucadmin .aut-' + affiliation + ' .aut-add').after(
'<div class="one-aut ' + hash + '">' +
'<input id="aut-' + affiliation + '" name="' + affiliation + '" type="text" class="mucadmin-i" value="' + xid + '" />' +
'<a href="#" class="aut-remove">[-]</a>' +
'</div>'
);
// Click event
$('#mucadmin .' + hash + ' .aut-remove').click(function() {
return removeInputMucAdmin(this);
});
// Focus on the input we added
if(!xid)
$(document).oneTime(10, function() {
$('#mucadmin .' + hash + ' input').focus();
});
return false;
}
// Handles the MUC admin form
function handleMucAdminAuth(iq) {
// We got the authorizations results
$(iq.getQuery()).find('item').each(function() {
// We parse the received xml
var xid = $(this).attr('jid');
var affiliation = $(this).attr('affiliation');
// We create one input for one XID
addInputMucAdmin(xid, affiliation);
});
// Hide the wait icon
$('#mucadmin .wait').hide();
logThis('MUC admin items received: ' + fullXID(getStanzaFrom(iq)));
}
// Queries the MUC admin form
function queryMucAdmin(xid, type) {
// Show the wait icon
$('#mucadmin .wait').show();
// New IQ
var iq = new JSJaCIQ();
iq.setTo(xid);
iq.setType('get');
var iqQuery = iq.setQuery(NS_MUC_ADMIN);
iqQuery.appendChild(iq.buildNode('item', {'affiliation': type, 'xmlns': NS_MUC_ADMIN}));
con.send(iq, handleMucAdminAuth);
}
// Sends the new chat-room topic
function sendMucAdminTopic(xid) {
// We get the new topic
var topic = $('.mucadmin-topic textarea').val();
// We send the new topic if not blank
if(topic) {
var m = new JSJaCMessage();
m.setTo(xid);
m.setType('groupchat');
m.setSubject(topic);
con.send(m);
logThis('MUC admin topic sent: ' + topic, 3);
}
}
// Sends the MUC admin auth form
function sendMucAdminAuth(xid) {
// We define the values array
var types = new Array('member', 'owner', 'admin', 'outcast');
for(i in types) {
// We get the current type
var tType = types[i];
// We loop for all the elements
$('.mucadmin-aut .aut-' + tType + ' input').each(function() {
// We set the iq headers
var iq = new JSJaCIQ();
iq.setTo(xid);
iq.setType('set');
var iqQuery = iq.setQuery(NS_MUC_ADMIN);
// We get the needed values
var value = $(this).val();
// If there's a value
if(value)
var item = iqQuery.appendChild(iq.buildNode('item', {'jid': value, 'xmlns': NS_MUC_ADMIN}));
// It the user had removed the XID
if($(this).hasClass('aut-dustbin') && value)
item.setAttribute('affiliation', 'none');
// If the value is not blank and okay
else if(value)
item.setAttribute('affiliation', tType);
// We send the iq !
con.send(iq, handleErrorReply);
});
}
logThis('MUC admin authorizations form sent: ' + xid, 3);
}
// Checks if the MUC room was destroyed
function handleDestroyMucAdminIQ(iq) {
if(!handleErrorReply(iq)) {
// We close the groupchat
var room = fullXID(getStanzaFrom(iq));
var hash = hex_md5(room);
quitThisChat(room, hash, 'groupchat');
// We close the muc admin popup
closeMucAdmin();
// We tell the user that all is okay
openThisInfo(5);
// We remove the user's favorite
if(existDB('favorites', room))
removeThisFavorite(room, explodeThis('@', room, 0));
logThis('MUC admin destroyed: ' + room, 3);
}
// We hide the wait icon
$('#mucadmin .wait').hide();
}
// Destroys a MUC room
function destroyMucAdminIQ(xid) {
// We ask the server to delete the room
var iq = new JSJaCIQ();
iq.setTo(xid);
iq.setType('set');
var iqQuery = iq.setQuery(NS_MUC_OWNER);
iqQuery.appendChild(iq.buildNode('destroy', {'xmlns': NS_MUC_OWNER}));
con.send(iq, handleDestroyMucAdminIQ);
logThis('MUC admin destroy sent: ' + xid, 3);
return false;
}
// Performs the MUC room destroy functions
function destroyMucAdmin() {
// We get the XID of the current room
var xid = $('#mucadmin .mucadmin-head-jid').text();
// We show the wait icon
$('#mucadmin .wait').show();
// We send the iq
destroyMucAdminIQ(xid);
}
// Sends all the MUC admin stuffs
function sendMucAdmin() {
// We get the XID of the current room
var xid = $('#mucadmin .mucadmin-head-jid').text();
// We change the room topic
sendMucAdminTopic(xid);
// We send the needed queries
sendDataForm('x', 'submit', 'submit', $('#mucadmin .mucadmin-results').attr('data-session'), xid, '', '', 'mucadmin');
sendMucAdminAuth(xid);
}
// Saves the MUC admin elements
function saveMucAdmin() {
// We send the new options
sendMucAdmin();
// And we quit the popup
return closeMucAdmin();
}
// Plugin launcher
function launchMucAdmin() {
// Click events
$('#mucadmin .bottom .finish').click(function() {
if($(this).is('.cancel'))
return closeMucAdmin();
if($(this).is('.save'))
return saveMucAdmin();
});
}
/*
Jappix - An open social platform
These are the connection JS script for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda
Last revision: 20/02/12
*/
// Does the user login
var CURRENT_SESSION = false;
function doLogin(lNick, lServer, lPass, lResource, lPriority, lRemember, loginOpts) {
try {
// get optionnal conn handlers
oExtend = loginOpts || {};
// We remove the not completed class to avoid problems
$('#home .loginer input').removeClass('please-complete');
// We add the login wait div
showGeneralWait();
// We define the http binding parameters
oArgs = new Object();
if(HOST_BOSH_MAIN)
oArgs.httpbase = HOST_BOSH_MAIN;
else
oArgs.httpbase = HOST_BOSH;
// Check BOSH origin
BOSH_SAME_ORIGIN = isSameOrigin(oArgs.httpbase);
// We create the new http-binding connection
con = new JSJaCHttpBindingConnection(oArgs);
// And we handle everything that happen
setupCon(con,oExtend);
// Generate a resource
var random_resource = getDB('session', 'resource');
if(!random_resource)
random_resource = lResource + ' (' + (new Date()).getTime() + ')';
// We retrieve what the user typed in the login inputs
oArgs = new Object();
oArgs.domain = trim(lServer);
oArgs.username = trim(lNick);
oArgs.resource = random_resource;
oArgs.pass = lPass;
oArgs.secure = true;
oArgs.xmllang = XML_LANG;
// Store the resource (for reconnection)
setDB('session', 'resource', random_resource);
// Store session XML in temporary database
storeSession(lNick, lServer, lPass, lResource, lPriority, lRemember);
// We store the infos of the user into the data-base
setDB('priority', 1, lPriority);
// We connect !
con.connect(oArgs);
// Change the page title
pageTitle('wait');
logThis('Jappix is connecting...', 3);
}
catch(e) {
// Logs errors
logThis('Error while logging in: ' + e, 1);
// Reset Jappix
destroyTalkPage();
// Open an unknown error
openThisError(2);
}
finally {
return false;
}
}
// Handles the user registration
function handleRegistered() {
logThis('A new account has been registered.', 3);
// We remove the waiting image
removeGeneralWait();
// Reset the title
pageTitle('home');
// We show the success information
$('#home .registerer .success').fadeIn('fast');
// We quit the session
if(isConnected())
logout();
}
// Does the user registration
function doRegister(username, domain, pass, captcha) {
logThis('Trying to register an account...', 3);
// We change the registered information text
$('#home .homediv.registerer').append(
'<div class="info success">' +
_e("You have been registered, here is your XMPP address:") + ' <b>' + username.htmlEnc() + '@' + domain.htmlEnc() + '</b> - <a href="#">' + _e("Login") + '</a>' +
'</div>'
);
// Login link
$('#home .homediv.registerer .success a').click(function() {
return doLogin(username, domain, pass, '', '10', false);
});
if((REGISTER_API == 'on') && (domain == HOST_MAIN) && captcha) {
// Show the waiting image
showGeneralWait();
// Change the page title
pageTitle('wait');
// Send request
$.post('./php/register.php', {username: username, domain: domain, password: pass, captcha: captcha}, function(data) {
// Error registering
removeGeneralWait();
pageTitle('home');
// In all case, update CAPTCHA
$('#home img.captcha_img').attr('src', './php/captcha.php?id=' + genID());
$('#home input.captcha').val('');
// Registration okay
if($(data).find('query status').text() == '1') {
handleRegistered();
} else {
// Show error message
var error_message = '';
switch($(data).find('query message').text()) {
case 'CAPTCHA Not Matching':
error_message = _e("The security code you entered is invalid. Please retry with another one.");
$('#home input.captcha').focus();
break;
case 'Username Unavailable':
error_message = _e("The username you picked is not available. Please try another one.");
$('#home input.nick').focus();
break;
default:
error_message = _e("There was an error registering your account. Please retry.");
break;
}
if(error_message)
showError('', error_message, '');
}
});
} else {
try {
// We define the http binding parameters
oArgs = new Object();
if(HOST_BOSH_MAIN)
oArgs.httpbase = HOST_BOSH_MAIN;
else
oArgs.httpbase = HOST_BOSH;
// Check BOSH origin
BOSH_SAME_ORIGIN = isSameOrigin(oArgs.httpbase);
// We create the new http-binding connection
con = new JSJaCHttpBindingConnection(oArgs);
// We setup the connection !
con.registerHandler('onconnect', handleRegistered);
con.registerHandler('onerror', handleError);
// We retrieve what the user typed in the register inputs
oArgs = new Object();
oArgs.domain = trim(domain);
oArgs.username = trim(username);
oArgs.resource = JAPPIX_RESOURCE + ' Register (' + (new Date()).getTime() + ')';
oArgs.pass = pass;
oArgs.register = true;
oArgs.secure = true;
oArgs.xmllang = XML_LANG;
con.connect(oArgs);
// Show the waiting image
showGeneralWait();
// Change the page title
pageTitle('wait');
}
catch(e) {
// Logs errors
logThis(e, 1);
}
}
return false;
}
// Does the user anonymous login
function doAnonymous() {
logThis('Trying to login anonymously...', 3);
var aPath = '#home .anonymouser ';
var room = $(aPath + '.room').val();
var nick = $(aPath + '.nick').val();
// If the form is correctly completed
if(room && nick) {
// We remove the not completed class to avoid problems
$('#home .anonymouser input').removeClass('please-complete');
// Redirect the user to the anonymous room
window.location.href = JAPPIX_LOCATION + '?r=' + room + '&n=' + nick;
}
// We check if the form is entirely completed
else {
$(aPath + 'input[type="text"]').each(function() {
var select = $(this);
if(!select.val())
$(document).oneTime(10, function() {
select.addClass('please-complete').focus();
});
else
select.removeClass('please-complete');
});
}
return false;
}
// Handles the user connected event
var CONNECTED = false;
function handleConnected() {
logThis('Jappix is now connected.', 3);
// Connection markers
CONNECTED = true;
RECONNECT_TRY = 0;
RECONNECT_TIMER = 0;
// We hide the home page
$('#home').hide();
// Any suggest to do before triggering connected event?
suggestCheck();
// Remove the waiting item
removeGeneralWait();
}
// Triggers the connected state
function triggerConnected() {
try {
// Not resumed?
if(!RESUME) {
// Remember the session?
if(getDB('remember', 'session'))
setPersistent('global', 'session', 1, CURRENT_SESSION);
// We show the chatting app.
createTalkPage();
// We reset the homepage
switchHome('default');
// We get all the other things
getEverything();
// Set last activity stamp
LAST_ACTIVITY = getTimeStamp();
}
// Resumed
else {
// Send our presence
presenceSend();
// Change the title
updateTitle();
}
} catch(e) {
logThis('Error in triggerConnected() with message: ' + e, 1);
}
}
// Handles the user disconnected event
function handleDisconnected() {
logThis('Jappix is now disconnected.', 3);
// Normal disconnection
if(!CURRENT_SESSION && !CONNECTED)
destroyTalkPage();
}
// Setups the normal connection
function setupCon(con, oExtend) {
// Setup connection handlers
con.registerHandler('message', handleMessage);
con.registerHandler('presence', handlePresence);
con.registerHandler('iq', handleIQ);
con.registerHandler('onconnect', handleConnected);
con.registerHandler('onerror', handleError);
con.registerHandler('ondisconnect', handleDisconnected);
// Extended handlers
oExtend = oExtend || {};
jQuery.each(oExtend, function(keywd,funct) {
con.registerHandler(keywd, funct);
});
}
// Logouts from the server
function logout() {
// We are not connected
if(!isConnected())
return false;
// Disconnect from the XMPP server
con.disconnect();
logThis('Jappix is disconnecting...', 3);
}
// Terminates a session
function terminate() {
if(!isConnected())
return;
// Clear temporary session storage
resetConMarkers();
// Show the waiting item (useful if BOSH is sloooow)
showGeneralWait();
// Change the page title
pageTitle('wait');
// Disconnect from the XMPP server
logout();
}
// Quitss a session
function quit() {
if(!isConnected())
return;
// We show the waiting image
showGeneralWait();
// Change the page title
pageTitle('wait');
// We disconnect from the XMPP server
logout();
}
// Creates the reconnect pane
var RECONNECT_TRY = 0;
var RECONNECT_TIMER = 0;
function createReconnect(mode) {
logThis('This is not a normal disconnection, show the reconnect pane...', 1);
// Reconnect pane not yet displayed?
if(!exists('#reconnect')) {
// Blur the focused input/textarea/select
$('input, select, textarea').blur();
// Create the HTML code
var html = '<div id="reconnect" class="lock">' +
'<div class="pane">' +
_e("Due to a network issue, you were disconnected. What do you want to do now?");
// Can we cancel reconnection?
if(mode == 'normal')
html += '<a href="#" class="finish cancel">' + _e("Cancel") + '</a>';
html += '<a href="#" class="finish reconnect">' + _e("Reconnect") + '</a>' +
'</div></div>';
// Append the code
$('body').append(html);
// Click events
if(mode == 'normal')
$('#reconnect a.finish.cancel').click(function() {
return cancelReconnect();
});
$('#reconnect a.finish.reconnect').click(function() {
return acceptReconnect(mode);
});
// Try to reconnect automatically after a while
if(RECONNECT_TRY < 5)
RECONNECT_TIMER = 5 + (5 * RECONNECT_TRY);
else
RECONNECT_TIMER = 120;
// Change the try number
RECONNECT_TRY++;
// Fire the event!
$('#reconnect a.finish.reconnect').everyTime('1s', function() {
// We can reconnect!
if(RECONNECT_TIMER == 0)
return acceptReconnect(mode);
// Button text
if(RECONNECT_TIMER <= 10)
$(this).text(_e("Reconnect") + ' (' + RECONNECT_TIMER + ')');
// Remove 1 second
RECONNECT_TIMER--;
});
// Page title
updateTitle();
}
}
// Reconnects the user if he was disconnected (network issue)
var RESUME = false;
function acceptReconnect(mode) {
logThis('Trying to reconnect the user...', 3);
// Resume marker
RESUME = true;
// Show waiting item
showGeneralWait();
// Reset some various stuffs
var groupchats = '#page-engine .page-engine-chan[data-type="groupchat"]';
$(groupchats + ' .list .role').hide();
$(groupchats + ' .one-group, ' + groupchats + ' .list .user').remove();
$(groupchats).attr('data-initial', 'false');
// Stop the timer
$('#reconnect a.finish.reconnect').stopTime();
// Remove the reconnect pane
$('#reconnect').remove();
// Try to login again
if(mode == 'normal')
loginFromSession(XMLFromString(CURRENT_SESSION));
else if(mode == 'anonymous')
anonymousLogin(HOST_ANONYMOUS);
return false;
}
// Cancel the reconnection of user account (network issue)
function cancelReconnect() {
logThis('User has canceled automatic reconnection...', 3);
// Stop the timer
$('#reconnect a.finish.reconnect').stopTime();
// Remove the reconnect pane
$('#reconnect').remove();
// Destroy the talk page
destroyTalkPage();
// Renitialize the previous session parameters
resetConMarkers();
return false;
}
// Clears session reminder database
function clearLastSession() {
// Clear temporary storage
resetConMarkers();
// Clear persistent storage
if($(XMLFromString(getPersistent('global', 'session', 1))).find('stored').text() == 'true')
removePersistent('global', 'session', 1);
}
// Resets the connection markers
function resetConMarkers() {
CURRENT_SESSION = false;
CONNECTED = false;
RESUME = false;
RECONNECT_TRY = 0;
RECONNECT_TIMER = 0;
}
// Logins from a saved session
function loginFromSession(data) {
// Select the data
var session = $(data);
// Fire the login event
doLogin(
session.find('username').text(),
session.find('domain').text(),
session.find('password').text(),
session.find('resource').text(),
session.find('priority').text(),
false
);
}
// Quits a session normally
function normalQuit() {
// Reset our database
clearLastSession();
// We quit the current session
quit();
// We show an info
openThisInfo(3);
return false;
}
// Gets all the users stuffs
function getEverything() {
getFeatures();
getRoster();
listPrivacy();
getStorage(NS_ROSTERNOTES);
}
// Generates session data to store
function storeSession(lNick, lServer, lPass, lResource, lPriority, lRemember) {
// Generate a session XML to be stored
session_xml = '<session><stored>true</stored><domain>' + lServer.htmlEnc() + '</domain><username>' + lNick.htmlEnc() + '</username><resource>' + lResource.htmlEnc() + '</resource><password>' + lPass.htmlEnc() + '</password><priority>' + lPriority.htmlEnc() + '</priority></session>';
// Save the session parameters (for reconnect if network issue)
CURRENT_SESSION = session_xml;
// Remember me?
if(lRemember)
setDB('remember', 'session', 1);
return session_xml;
}
// Plugin launcher
function launchConnection() {
// Logouts when Jappix is closed
$(window).bind('beforeunload', terminate);
// Nothing to do when anonymous!
if(isAnonymous())
return;
// Connection params submitted in URL?
if(LINK_VARS['u'] && LINK_VARS['q']) {
// Generate login data
var login_xid = bareXID(generateXID(LINK_VARS['u'], 'chat'));
var login_nick = getXIDNick(login_xid);
var login_server = getXIDHost(login_xid);
var login_pwd = LINK_VARS['q'];
var login_resource = JAPPIX_RESOURCE + ' (' + (new Date()).getTime() + ')';
var login_priority = '10';
var login_remember = 1;
// Must store session?
if(LINK_VARS['h'] && (LINK_VARS['h'] == '1')) {
// Store session
var session_xml = storeSession(login_nick, login_server, login_pwd, login_resource, login_priority, true);
setPersistent('global', 'session', 1, session_xml);
// Redirect to a clean URL
document.location.href = './';
} else {
// Hide the homepage
$('#home').hide();
// Show the waiting icon
showGeneralWait();
// Proceed login
doLogin(login_nick, login_server, login_pwd, login_resource, login_priority, login_remember);
}
return;
}
// Try to resume a stored session, if not anonymous
var session = XMLFromString(getPersistent('global', 'session', 1));
if($(session).find('stored').text() == 'true') {
// Hide the homepage
$('#home').hide();
// Show the waiting icon
showGeneralWait();
// Login!
loginFromSession(session);
logThis('Saved session found, resuming it...', 3);
}
// Not connected, maybe a XMPP link is submitted?
else if((parent.location.hash != '#OK') && LINK_VARS['x']) {
switchHome('loginer');
logThis('A XMPP link is set, switch to login page.', 3);
}
}
// Launch this plugin!
$(document).ready(launchConnection);
/*
Jappix - An open social platform
These are the dataform JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda
Last revision: 19/09/12
*/
// Gets the defined dataform elements
function dataForm(host, type, node, action, target) {
// Clean the current session
cleanDataForm(target);
// We tell the user that a search has been launched
$('#' + target + ' .wait').show();
// If we have enough data
if(host && type) {
// Generate a session ID
var sessionID = Math.round(100000.5 + (((900000.49999) - (100000.5)) * Math.random()));
var id = target + '-' + sessionID + '-' + genID();
$('.' + target + '-results').attr('data-session', target + '-' + sessionID);
// We request the service item
var iq = new JSJaCIQ();
iq.setID(id);
iq.setTo(host);
iq.setType('get');
// MUC admin query
if(type == 'muc') {
iq.setQuery(NS_MUC_OWNER);
con.send(iq, handleDataFormMuc);
}
// Browse query
else if(type == 'browse') {
var iqQuery = iq.setQuery(NS_DISCO_ITEMS);
if(node)
iqQuery.setAttribute('node', node);
con.send(iq, handleDataFormBrowse);
}
// Command
else if(type == 'command') {
var items;
if(node)
items = iq.appendNode('command', {'node': node, 'xmlns': NS_COMMANDS});
else {
items = iq.setQuery(NS_DISCO_ITEMS);
items.setAttribute('node', NS_COMMANDS);
}
if(action && node) {
iq.setType('set');
items.setAttribute('action', action);
}
con.send(iq, handleDataFormCommand);
}
// Search query
else if(type == 'search') {
iq.setQuery(NS_SEARCH);
con.send(iq, handleDataFormSearch);
}
// Subscribe query
else if(type == 'subscribe') {
iq.setQuery(NS_REGISTER);
con.send(iq, handleDataFormSubscribe);
}
// Join
else if(type == 'join') {
if(target == 'discovery')
closeDiscovery();
checkChatCreate(host, 'groupchat');
}
}
return false;
}
// Sends a given dataform
function sendDataForm(type, action, x_type, id, xid, node, sessionid, target) {
// Path
var pathID = '#' + target + ' .results[data-session="' + id + '"]';
// New IQ
var iq = new JSJaCIQ();
iq.setTo(xid);
iq.setType('set');
// Set the correct query
var query;
if(type == 'subscribe')
iqQuery = iq.setQuery(NS_REGISTER);
else if(type == 'search')
iqQuery = iq.setQuery(NS_SEARCH);
else if(type == 'command')
iqQuery = iq.appendNode('command', {'xmlns': NS_COMMANDS, 'node': node, 'sessionid': sessionid, 'action': action});
else if(type == 'x')
iqQuery = iq.setQuery(NS_MUC_OWNER);
// Build the XML document
if(action != 'cancel') {
// No X node
if(exists('input.register-special') && (type == 'subscribe')) {
$('input.register-special').each(function() {
var iName = $(this).attr('name');
var iValue = $(this).val();
iqQuery.appendChild(iq.buildNode(iName, {'xmlns': NS_REGISTER}, iValue));
});
}
// Can create the X node
else {
var iqX = iqQuery.appendChild(iq.buildNode('x', {'xmlns': NS_XDATA, 'type': x_type}));
// Each input
$(pathID + ' .oneresult input, ' + pathID + ' .oneresult textarea, ' + pathID + ' .oneresult select').each(function() {
// Get the current input value
var iVar = $(this).attr('name');
var iType = $(this).attr('data-type');
var iValue = $(this).val();
// Build a new field node
var field = iqX.appendChild(iq.buildNode('field', {'var': iVar, 'type': iType, 'xmlns': NS_XDATA}));
// Boolean input?
if(iType == 'boolean') {
if($(this).filter(':checked').size())
iValue = '1';
else
iValue = '0';
}
// JID-multi input?
if(iType == 'jid-multi') {
// Values array
var xid_arr = [iValue];
var xid_check = [];
// Try to split it
if(iValue.indexOf(',') != -1)
xid_arr = iValue.split(',');
// Append each value to the XML document
for(i in xid_arr) {
// Get the current value
xid_current = trim(xid_arr[i]);
// No current value?
if(!xid_current)
continue;
// Add the current value
if(!existArrayValue(xid_check, xid_current)) {
xid_check.push(xid_current);
field.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, xid_current));
}
}
}
// List-multi selector?
else if(iType == 'list-multi') {
// Any value?
if(iValue && iValue.length) {
for(i in iValue)
field.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, iValue[i]));
}
}
// Other inputs?
else
field.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, iValue));
});
}
}
// Clean the current session
cleanDataForm(target);
// Show the waiting item
$('#' + target + ' .wait').show();
// Change the ID of the current discovered item
var iqID = target + '-' + genID();
$('#' + target + ' .' + target + '-results').attr('data-session', iqID);
iq.setID(iqID);
// Send the IQ
if(type == 'subscribe')
con.send(iq, handleDataFormSubscribe);
else if(type == 'search')
con.send(iq, handleDataFormSearch);
else if(type == 'command')
con.send(iq, handleDataFormCommand);
else
con.send(iq);
return false;
}
// Displays the good dataform buttons
function buttonsDataForm(type, action, id, xid, node, sessionid, target, pathID) {
// No need to use buttons?
if(type == 'muc')
return;
// Override the "undefined" output
if(!id)
id = '';
if(!xid)
xid = '';
if(!node)
node = '';
if(!sessionid)
sessionid = '';
// We generate the buttons code
var buttonsCode = '<div class="oneresult ' + target + '-oneresult ' + target + '-formtools">';
if(action == 'submit') {
if((target == 'adhoc') && (type == 'command')) {
buttonsCode += '<a href="#" class="submit" onclick="return sendDataForm(\'' + encodeOnclick(type) + '\', \'execute\', \'submit\', \'' + encodeOnclick(id) + '\', \'' + encodeOnclick(xid) + '\', \'' + encodeOnclick(node) + '\', \'' + encodeOnclick(sessionid) + '\', \'' + encodeOnclick(target) + '\');">' + _e("Submit") + '</a>';
// When keyup on one text input
$(pathID + ' input').keyup(function(e) {
if(e.keyCode == 13) {
sendDataForm(type, 'execute', 'submit', id, xid, node, sessionid, target);
return false;
}
});
}
else {
buttonsCode += '<a href="#" class="submit" onclick="return sendDataForm(\'' + encodeOnclick(type) + '\', \'submit\', \'submit\', \'' + encodeOnclick(id) + '\', \'' + encodeOnclick(xid) + '\', \'' + encodeOnclick(node) + '\', \'' + encodeOnclick(sessionid) + '\', \'' + encodeOnclick(target) + '\');">' + _e("Submit") + '</a>';
// When keyup on one text input
$(pathID + ' input').keyup(function(e) {
if(e.keyCode == 13) {
sendDataForm(type, 'submit', 'submit', id, xid, node, sessionid, target);
return false;
}
});
}
}
if((action == 'submit') && (type != 'subscribe') && (type != 'search'))
buttonsCode += '<a href="#" class="submit" onclick="return sendDataForm(\'' + encodeOnclick(type) + '\', \'cancel\', \'cancel\', \'' + encodeOnclick(id) + '\', \'' + encodeOnclick(xid) + '\', \'' + encodeOnclick(node) + '\', \'' + encodeOnclick(sessionid) + '\', \'' + encodeOnclick(target) + '\');">' + _e("Cancel") + '</a>';
if(((action == 'back') || (type == 'subscribe') || (type == 'search')) && (target == 'discovery'))
buttonsCode += '<a href="#" class="back" onclick="return startDiscovery();">' + _e("Close") + '</a>';
if((action == 'back') && ((target == 'welcome') || (target == 'directory')))
buttonsCode += '<a href="#" class="back" onclick="return dataForm(HOST_VJUD, \'search\', \'\', \'\', \'' + target + '\');">' + _e("Previous") + '</a>';
if((action == 'back') && (target == 'adhoc'))
buttonsCode += '<a href="#" class="back" onclick="return dataForm(\'' + encodeOnclick(xid) + '\', \'command\', \'\', \'\', \'adhoc\');">' + _e("Previous") + '</a>';
buttonsCode += '</div>';
// We display the buttons code
$(pathID).append(buttonsCode);
// If no submit link, lock the form
if(!exists(pathID + ' a.submit'))
$(pathID + ' input, ' + pathID + ' textarea').attr('readonly', true);
}
// Handles the MUC dataform
function handleDataFormMuc(iq) {
handleErrorReply(iq);
handleDataFormContent(iq, 'muc');
}
// Handles the browse dataform
function handleDataFormBrowse(iq) {
handleErrorReply(iq);
handleDataFormContent(iq, 'browse');
}
// Handles the command dataform
function handleDataFormCommand(iq) {
handleErrorReply(iq);
handleDataFormContent(iq, 'command');
}
// Handles the subscribe dataform
function handleDataFormSubscribe(iq) {
handleErrorReply(iq);
handleDataFormContent(iq, 'subscribe');
}
// Handles the search dataform
function handleDataFormSearch(iq) {
handleErrorReply(iq);
handleDataFormContent(iq, 'search');
}
// Handles the dataform content
function handleDataFormContent(iq, type) {
// Get the ID
var sID = iq.getID();
// Get the target
var splitted = sID.split('-');
var target = splitted[0];
var sessionID = target + '-' + splitted[1];
var from = fullXID(getStanzaFrom(iq));
var pathID = '#' + target + ' .results[data-session="' + sessionID + '"]';
// If an error occured
if(!iq || (iq.getType() != 'result'))
noResultDataForm(pathID);
// If we got something okay
else {
var handleXML = iq.getNode();
if(type == 'browse') {
if($(handleXML).find('item').attr('jid')) {
// Get the query node
var queryNode = $(handleXML).find('query').attr('node');
$(handleXML).find('item').each(function() {
// We parse the received xml
var itemHost = $(this).attr('jid');
var itemNode = $(this).attr('node');
var itemName = $(this).attr('name');
var itemHash = hex_md5(itemHost);
// Node
if(itemNode)
$(pathID).append(
'<div class="oneresult ' + target + '-oneresult" onclick="return dataForm(\'' + encodeOnclick(itemHost) + '\', \'browse\', \'' + encodeOnclick(itemNode) + '\', \'\', \'' + encodeOnclick(target) + '\');">' +
'<div class="one-name">' + itemNode.htmlEnc() + '</div>' +
'</div>'
);
// Item
else if(queryNode && itemName)
$(pathID).append(
'<div class="oneresult ' + target + '-oneresult">' +
'<div class="one-name">' + itemName.htmlEnc() + '</div>' +
'</div>'
);
// Item with children
else {
// We display the waiting element
$(pathID + ' .disco-wait .disco-category-title').after(
'<div class="oneresult ' + target + '-oneresult ' + itemHash + '">' +
'<div class="one-icon loading talk-images"></div>' +
'<div class="one-host">' + itemHost + '</div>' +
'<div class="one-type">' + _e("Requesting this service...") + '</div>' +
'</div>'
);
// We display the category
$('#' + target + ' .disco-wait').show();
// We ask the server what's the service type
getDataFormType(itemHost, itemNode, sessionID);
}
});
}
// Else, there are no items for this query
else
noResultDataForm(pathID);
}
else if((type == 'muc') || (type == 'search') || (type == 'subscribe') || ((type == 'command') && $(handleXML).find('command').attr('xmlns'))) {
// Get some values
var xCommand = $(handleXML).find('command');
var bNode = xCommand.attr('node');
var bSession = xCommand.attr('sessionid');
var bStatus = xCommand.attr('status');
var xRegister = $(handleXML).find('query[xmlns="' + NS_REGISTER + '"]').text();
var xElement = $(handleXML).find('x');
// Search done
if((xElement.attr('type') == 'result') && (type == 'search')) {
var bPath = pathID;
// Display the result
$(handleXML).find('item').each(function() {
// Have some "flexibility" for what regards field names, it would be better to return the whole original DF
// layout, but on a large amount of result which have many fields, there's a very high chance the browser can
// choke on old systems or new ones even.
// Search for useful fields, return first result. This is rather hacky, but jQuery is horrible when it comes to
// matching st. using patterns. (TODO: Improve and return the full DF layout without choking the browser)
var bName;
var bCountry;
var doneName, doneCountry;
$.each($(this).find('field'), function(i, item)
{
var $item = $(item);
if ($(item).attr('var').match(/^(fn|name|[^n][^i][^c][^k]name)$/gi) && doneName != true) {
bName = $item.children('value:first').text();
doneName = true;
} else if ($(item).attr('var').match(/^(ctry|country.*)$/gi) && doneCountry != true) {
bCountry = $item.children('value:first').text();
doneCountry = true;
}
});
var bXID = $(this).find('field[var="jid"] value:first').text();
var dName = bName;
// Override "undefined" value
if(!bXID)
bXID = '';
if(!bName)
bName = _e("Unknown name");
if(!bCountry)
bCountry = _e("Unknown country");
// User hash
var bHash = hex_md5(bXID);
// HTML code
var bHTML = '<div class="oneresult ' + target + '-oneresult ' + bHash + '">' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'<div class="one-fn">' + bName + '</div>' +
'<div class="one-ctry">' + bCountry + '</div>' +
'<div class="one-jid">' + bXID + '</div>' +
'<div class="buttons-container">';
// The buddy is not in our buddy list?
if(!exists('#buddy-list .buddy[data-xid="' + escape(bXID) + '"]'))
bHTML += '<a href="#" class="one-add one-vjud one-button talk-images">' + _e("Add") + '</a>';
// Chat button, if not in welcome/directory mode
if(target == 'discovery')
bHTML += '<a href="#" class="one-chat one-vjud one-button talk-images">' + _e("Chat") + '</a>';
// Profile button, if not in discovery mode
else
bHTML += '<a href="#" class="one-profile one-vjud one-button talk-images">' + _e("Profile") + '</a>';
// Close the HTML element
bHTML += '</div></div>';
$(bPath).append(bHTML);
// Click events
$(bPath + ' .' + bHash + ' a').click(function() {
// Buddy add
if($(this).is('.one-add')) {
$(this).hide();
addThisContact(bXID, dName);
}
// Buddy chat
if($(this).is('.one-chat')) {
if(target == 'discovery')
closeDiscovery();
checkChatCreate(bXID, 'chat', '', '', dName);
}
// Buddy profile
if($(this).is('.one-profile'))
openUserInfos(bXID);
return false;
});
// Get the user's avatar
if(bXID)
getAvatar(bXID, 'cache', 'true', 'forget');
});
// No result?
if(!$(handleXML).find('item').size())
noResultDataForm(pathID);
// Previous button
buttonsDataForm(type, 'back', sessionID, from, bNode, bSession, target, pathID);
}
// Command to complete
else if(xElement.attr('xmlns') || ((type == 'subscribe') && xRegister)) {
// We display the elements
fillDataForm(handleXML, sessionID);
// We display the buttons
if(bStatus != 'completed')
buttonsDataForm(type, 'submit', sessionID, from, bNode, bSession, target, pathID);
else
buttonsDataForm(type, 'back', sessionID, from, bNode, bSession, target, pathID);
}
// Command completed or subscription done
else if(((bStatus == 'completed') && (type == 'command')) || (!xRegister && (type == 'subscribe'))) {
// Display the good text
var cNote = $(xCommand).find('note');
// Any note?
if(cNote.size()) {
cNote.each(function() {
$(pathID).append(
'<div class="onetitle ' + target + '-oneresult">' + $(this).text().htmlEnc() + '</div>'
);
});
}
// Default text
else
$(pathID).append('<div class="oneinstructions ' + target + '-oneresult">' + _e("Your form has been sent.") + '</div>');
// Display the back button
buttonsDataForm(type, 'back', sessionID, from, '', '', target, pathID);
// Add the gateway to our roster if subscribed
if(type == 'subscribe')
addThisContact(from);
}
// Command canceled
else if((bStatus == 'canceled') && (type == 'command')) {
if(target == 'discovery')
startDiscovery();
else if(target == 'adhoc')
dataForm(from, 'command', '', '', 'adhoc');
}
// No items for this query
else
noResultDataForm(pathID);
}
else if(type == 'command') {
if($(handleXML).find('item').attr('jid')) {
// We display the elements
$(handleXML).find('item').each(function() {
// We parse the received xml
var itemHost = $(this).attr('jid');
var itemNode = $(this).attr('node');
var itemName = $(this).attr('name');
var itemHash = hex_md5(itemHost);
// We display the waiting element
$(pathID).prepend(
'<div class="oneresult ' + target + '-oneresult ' + itemHash + '" onclick="return dataForm(\'' + encodeOnclick(itemHost) + '\', \'command\', \'' + encodeOnclick(itemNode) + '\', \'execute\', \'' + encodeOnclick(target) + '\');">' +
'<div class="one-name">' + itemName + '</div>' +
'<div class="one-next">»</div>' +
'</div>'
);
});
}
// Else, there are no items for this query
else
noResultDataForm(pathID);
}
}
// Focus on the first input
$(document).oneTime(10, function() {
$(pathID + ' input:visible:first').focus();
});
// Hide the wait icon
$('#' + target + ' .wait').hide();
}
// Fills the dataform elements
function fillDataForm(xml, id) {
/* REF: http://xmpp.org/extensions/xep-0004.html */
// Initialize new vars
var target = id.split('-')[0];
var pathID = '#' + target + ' .results[data-session="' + id + '"]';
var selector, is_dataform;
// Is it a dataform?
if($(xml).find('x[xmlns="' + NS_XDATA + '"]').size())
is_dataform = true;
else
is_dataform = false;
// Determines the good selector to use
if(is_dataform)
selector = $(xml).find('x[xmlns="' + NS_XDATA + '"]');
else
selector = $(xml);
// Form title
selector.find('title').each(function() {
$(pathID).append(
'<div class="onetitle ' + target + '-oneresult">' + $(this).text().htmlEnc() + '</div>'
);
});
// Form instructions
selector.find('instructions').each(function() {
$(pathID).append(
'<div class="oneinstructions ' + target + '-oneresult">' + $(this).text().htmlEnc() + '</div>'
);
});
// Register?
if(!is_dataform) {
// Items to detect
var reg_names = [_e("Nickname"), _e("Name"), _e("Password"), _e("E-mail")];
var reg_ids = ['username', 'name', 'password', 'email'];
// Append these inputs
for(a in reg_names) {
selector.find(reg_ids[a]).each(function() {
$(pathID).append(
'<div class="oneresult ' + target + '-oneresult">' +
'<label>' + reg_names[a] + '</label>' +
'<input name="' + reg_ids[a] + '" type="text" class="register-special dataform-i" />' +
'</div>'
);
});
}
return false;
}
// Dataform?
selector.find('field').each(function() {
// We parse the received xml
var type = $(this).attr('type');
var label = $(this).attr('label');
var field = $(this).attr('var');
var value = $(this).find('value:first').text();
var required = '';
// No value?
if(!field)
return;
// Required input?
if($(this).find('required').size())
required = ' required=""';
// Compatibility fix
if(!label)
label = field;
if(!type)
type = '';
// Generate some values
var input;
var hideThis = '';
// Fixed field
if(type == 'fixed')
$(pathID).append('<div class="oneinstructions">' + value.htmlEnc() + '</div>');
else {
// Hidden field
if(type == 'hidden') {
hideThis = ' style="display: none;"';
input = '<input name="' + encodeQuotes(field) + '" data-type="' + encodeQuotes(type) + '" type="hidden" class="dataform-i" value="' + encodeQuotes(value) + '" ' + required + ' />';
}
// Boolean field
else if(type == 'boolean') {
var checked;
if(value == '1')
checked = 'checked';
else
checked = '';
input = '<input name="' + encodeQuotes(field) + '" type="checkbox" data-type="' + encodeQuotes(type) + '" class="dataform-i df-checkbox" ' + checked + required + ' />';
}
// List-single/list-multi field
else if((type == 'list-single') || (type == 'list-multi')) {
var multiple = '';
// Multiple options?
if(type == 'list-multi')
multiple = ' multiple=""';
// Append the select field
input = '<select name="' + encodeQuotes(field) + '" data-type="' + encodeQuotes(type) + '" class="dataform-i"' + required + multiple + '>';
var selected;
// Append the available options
$(this).find('option').each(function() {
var nLabel = $(this).attr('label');
var nValue = $(this).find('value').text();
// No label?
if(!nLabel)
nLabel = nValue;
// If this is the selected value
if(nValue == value)
selected = 'selected';
else
selected = '';
input += '<option ' + selected + ' value="' + encodeQuotes(nValue) + '">' + nLabel.htmlEnc() + '</option>';
});
input += '</select>';
}
// Text-multi field
else if(type == 'text-multi')
input = '<textarea rows="8" cols="60" data-type="' + encodeQuotes(type) + '" name="' + encodeQuotes(field) + '" class="dataform-i"' + required + '>' + value.htmlEnc() + '</textarea>';
// JID-multi field
else if(type == 'jid-multi') {
// Put the XID into an array
var xid_arr = [];
$(this).find('value').each(function() {
var cValue = $(this).text();
if(!existArrayValue(xid_arr, cValue))
xid_arr.push(cValue);
});
// Sort the array
xid_arr.sort();
// Create the input
var xid_value = '';
if(xid_arr.length) {
for(i in xid_arr) {
// Any pre-value
if(xid_value)
xid_value += ', ';
// Add the current XID
xid_value += xid_arr[i];
}
}
input = '<input name="' + encodeQuotes(field) + '" data-type="' + encodeQuotes(type) + '" type="text" class="dataform-i" value="' + encodeQuotes(xid_value) + '" placeholder="jack@jappix.com, jones@jappix.com"' + required + ' />';
}
// Other stuffs that are similar
else {
// Text-single field
var iType = 'text';
// Text-private field
if(type == 'text-private')
iType = 'password';
// JID-single field
else if(type == 'jid-single')
iType = 'email';
input = '<input name="' + encodeQuotes(field) + '" data-type="' + encodeQuotes(type) + '" type="' + iType + '" class="dataform-i" value="' + encodeQuotes(value) + '"' + required + ' />';
}
// Append the HTML markup for this field
$(pathID).append(
'<div class="oneresult ' + target + '-oneresult"' + hideThis + '>' +
'<label>' + label.htmlEnc() + '</label>' +
input +
'</div>'
);
}
});
return false;
}
// Gets the dataform type
function getDataFormType(host, node, id) {
var iq = new JSJaCIQ();
iq.setID(id + '-' + genID());
iq.setTo(host);
iq.setType('get');
var iqQuery = iq.setQuery(NS_DISCO_INFO);
if(node)
iqQuery.setAttribute('node', node);
con.send(iq, handleThisBrowse);
}
// Handles the browse stanza
function handleThisBrowse(iq) {
/* REF: http://xmpp.org/registrar/disco-categories.html */
var id = iq.getID();
var splitted = id.split('-');
var target = splitted[0];
var sessionID = target + '-' + splitted[1];
var from = fullXID(getStanzaFrom(iq));
var hash = hex_md5(from);
var handleXML = iq.getQuery();
var pathID = '#' + target + ' .results[data-session="' + sessionID + '"]';
// We first remove the waiting element
$(pathID + ' .disco-wait .' + hash).remove();
if($(handleXML).find('identity').attr('type')) {
var category = $(handleXML).find('identity').attr('category');
var type = $(handleXML).find('identity').attr('type');
var named = $(handleXML).find('identity').attr('name');
if(named)
gName = named;
else
gName = '';
var one, two, three, four, five;
// Get the features that this entity supports
var findFeature = $(handleXML).find('feature');
for(i in findFeature) {
var current = findFeature.eq(i).attr('var');
switch(current) {
case NS_SEARCH:
one = 1;
break;
case NS_MUC:
two = 1;
break;
case NS_REGISTER:
three = 1;
break;
case NS_COMMANDS:
four = 1;
break;
case NS_DISCO_ITEMS:
five = 1;
break;
default:
break;
}
}
var buttons = Array(one, two, three, four, five);
// We define the toolbox links depending on the supported features
var tools = '';
var aTools = Array('search', 'join', 'subscribe', 'command', 'browse');
var bTools = Array(_e("Search"), _e("Join"), _e("Subscribe"), _e("Command"), _e("Browse"));
for(i in buttons) {
if(buttons[i])
tools += '<a href="#" class="one-button ' + aTools[i] + ' talk-images" onclick="return dataForm(\'' + encodeOnclick(from) + '\', \'' + encodeOnclick(aTools[i]) + '\', \'\', \'\', \'' + encodeOnclick(target) + '\');" title="' + encodeOnclick(bTools[i]) + '"></a>';
}
// As defined in the ref, we detect the type of each category to put an icon
switch(category) {
case 'account':
case 'auth':
case 'automation':
case 'client':
case 'collaboration':
case 'component':
case 'conference':
case 'directory':
case 'gateway':
case 'headline':
case 'hierarchy':
case 'proxy':
case 'pubsub':
case 'server':
case 'store':
break;
default:
category = 'others';
}
// We display the item we found
$(pathID + ' .disco-' + category + ' .disco-category-title').after(
'<div class="oneresult ' + target + '-oneresult ' + hash + '">' +
'<div class="one-icon ' + category + ' talk-images"></div>' +
'<div class="one-host">' + from + '</div>' +
'<div class="one-type">' + gName + '</div>' +
'<div class="one-actions">' + tools + '</div>' +
'</div>'
);
// We display the category
$(pathID + ' .disco-' + category).show();
}
else {
$(pathID + ' .disco-others .disco-category-title').after(
'<div class="oneresult ' + target + '-oneresult">' +
'<div class="one-icon down talk-images"></div>' +
'<div class="one-host">' + from + '</div>' +
'<div class="one-type">' + _e("Service offline or broken") + '</div>' +
'</div>'
);
// We display the category
$(pathID + ' .disco-others').show();
}
// We hide the waiting stuffs if there's no remaining loading items
if(!$(pathID + ' .disco-wait .' + target + '-oneresult').size())
$(pathID + ' .disco-wait, #' + target + ' .wait').hide();
}
// Cleans the current data-form popup
function cleanDataForm(target) {
if(target == 'discovery')
cleanDiscovery();
else
$('#' + target + ' div.results').empty();
}
// Displays the no result indicator
function noResultDataForm(path) {
$(path).prepend('<p class="no-results">' + _e("Sorry, but the entity didn't return any result!") + '</p>');
}
/*
Jappix - An open social platform
These are the discovery JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 03/03/11
*/
// Opens the discovery popup
function openDiscovery() {
// Popup HTML content
var html =
'<div class="top">' + _e("Service discovery") + '</div>' +
'<div class="content">' +
'<div class="discovery-head">' +
'<div class="disco-server-text">' + _e("Server to query") + '</div>' +
'<input name="disco-server-input" class="disco-server-input" value="' + encodeQuotes(HOST_MAIN) + '" />' +
'</div>' +
'<div class="results discovery-results">' +
'<div class="disco-category disco-account">' +
'<p class="disco-category-title">' + _e("Accounts") + '</p>' +
'</div>' +
'<div class="disco-category disco-auth">' +
'<p class="disco-category-title">' + _e("Authentications") + '</p>' +
'</div>' +
'<div class="disco-category disco-automation">' +
'<p class="disco-category-title">' + _e("Automation") + '</p>' +
'</div>' +
'<div class="disco-category disco-client">' +
'<p class="disco-category-title">' + _e("Clients") + '</p>' +
'</div>' +
'<div class="disco-category disco-collaboration">' +
'<p class="disco-category-title">' + _e("Collaboration") + '</p>' +
'</div>' +
'<div class="disco-category disco-component">' +
'<p class="disco-category-title">' + _e("Components") + '</p>' +
'</div>' +
'<div class="disco-category disco-conference">' +
'<p class="disco-category-title">' + _e("Rooms") + '</p>' +
'</div>' +
'<div class="disco-category disco-directory">' +
'<p class="disco-category-title">' + _e("Directories") + '</p>' +
'</div>' +
'<div class="disco-category disco-gateway">' +
'<p class="disco-category-title">' + _e("Gateways") + '</p>' +
'</div>' +
'<div class="disco-category disco-headline">' +
'<p class="disco-category-title">' + _e("News") + '</p>' +
'</div>' +
'<div class="disco-category disco-hierarchy">' +
'<p class="disco-category-title">' + _e("Hierarchy") + '</p>' +
'</div>' +
'<div class="disco-category disco-proxy">' +
'<p class="disco-category-title">' + _e("Proxies") + '</p>' +
'</div>' +
'<div class="disco-category disco-pubsub">' +
'<p class="disco-category-title">' + _e("Publication/Subscription") + '</p>' +
'</div>' +
'<div class="disco-category disco-server">' +
'<p class="disco-category-title">' + _e("Server") + '</p>' +
'</div>' +
'<div class="disco-category disco-store">' +
'<p class="disco-category-title">' + _e("Storage") + '</p>' +
'</div>' +
'<div class="disco-category disco-others">' +
'<p class="disco-category-title">' + _e("Others") + '</p>' +
'</div>' +
'<div class="disco-category disco-wait">' +
'<p class="disco-category-title">' + _e("Loading") + '</p>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('discovery', html);
// Associate the events
launchDiscovery();
// We request a disco to the default server
startDiscovery();
return false;
}
// Quits the discovery popup
function closeDiscovery() {
// Destroy the popup
destroyPopup('discovery');
return false;
}
// Launches a discovery
function startDiscovery() {
/* REF: http://xmpp.org/extensions/xep-0030.html */
// We get the server to query
var discoServer = $('#discovery .disco-server-input').val();
// We launch the items query
dataForm(discoServer, 'browse', '', '', 'discovery');
logThis('Service discovery launched: ' + discoServer);
return false;
}
// Cleans the discovery results
function cleanDiscovery() {
// We remove the results
$('#discovery .discovery-oneresult, #discovery .oneinstructions, #discovery .onetitle, #discovery .no-results').remove();
// We clean the user info
$('#discovery .disco-server-info').text('');
// We hide the wait icon, the no result alert and the results
$('#discovery .wait, #discovery .disco-category').hide();
}
// Plugin launcher
function launchDiscovery() {
// Click event
$('#discovery .bottom .finish').click(closeDiscovery);
// Keyboard event
$('#discovery .disco-server-input').keyup(function(e) {
if(e.keyCode == 13) {
// No value?
if(!$(this).val())
$(this).val(HOST_MAIN);
// Start the discovery
startDiscovery();
return false;
}
});
}
/*
Jappix - An open social platform
These are the directory JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 03/03/11
*/
// Opens the directory popup
function openDirectory() {
// Popup HTML content
var html =
'<div class="top">' + _e("User directory") + '</div>' +
'<div class="content">' +
'<div class="directory-head">' +
'<div class="directory-server-text">' + _e("Server to query") + '</div>' +
'<input name="directory-server-input" class="directory-server-input" value="' + encodeQuotes(HOST_VJUD) + '" />' +
'</div>' +
'<div class="results directory-results"></div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('directory', html);
// Associate the events
launchDirectory();
// Start a search!
startDirectory();
return false;
}
// Quits the directory popup
function closeDirectory() {
// Destroy the popup
destroyPopup('directory');
return false;
}
// Launches a directory search
function startDirectory() {
// Get the server to query
var server = $('#directory .directory-server-input').val();
// Launch the search!
dataForm($('#directory .directory-server-input').val(), 'search', '', '', 'directory');
logThis('Directory search launched: ' + server);
return false;
}
// Plugin launcher
function launchDirectory() {
// Click event
$('#directory .bottom .finish').click(closeDirectory);
// Keyboard event
$('#directory .directory-server-input').keyup(function(e) {
if(e.keyCode == 13) {
// No value?
if(!$(this).val())
$(this).val(HOST_VJUD);
// Start the directory search
startDirectory();
return false;
}
});
}
/*
Jappix - An open social platform
These are the Ad-Hoc JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 11/07/11
*/
// Opens the adhoc popup
function openAdHoc() {
// Popup HTML content
var html =
'<div class="top">' + _e("Commands") + '</div>' +
'<div class="content">' +
'<div class="adhoc-head"></div>' +
'<div class="results adhoc-results"></div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('adhoc', html);
// Associate the events
launchAdHoc();
return false;
}
// Quits the adhoc popup
function closeAdHoc() {
// Destroy the popup
destroyPopup('adhoc');
return false;
}
// Retrieves an entity adhoc command
function retrieveAdHoc(xid) {
// Open the popup
openAdHoc();
// Add a XID marker
$('#adhoc .adhoc-head').html('<b>' + getBuddyName(xid).htmlEnc() + '</b> (' + xid.htmlEnc() + ')');
// Get the highest entity resource
var highest = highestPriority(xid);
if(highest)
xid = highest;
// Start a new adhoc command
dataForm(xid, 'command', '', '', 'adhoc');
return false;
}
// Starts an adhoc command on the user server
function serverAdHoc(server) {
// Open the popup
openAdHoc();
// Add a XID marker
$('#adhoc .adhoc-head').html('<b>' + server.htmlEnc() + '</b>');
// Start a new adhoc command
dataForm(server, 'command', '', '', 'adhoc');
}
// Plugin launcher
function launchAdHoc() {
// Click event
$('#adhoc .bottom .finish').click(closeAdHoc);
}
/*
Jappix - An open social platform
These are the privacy JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 20/07/13
*/
// Opens the privacy popup
function openPrivacy() {
// Popup HTML content
var html =
'<div class="top">' + _e("Privacy") + '</div>' +
'<div class="content">' +
'<div class="privacy-head">' +
'<div class="list-left">' +
'<span>' + _e("Choose") + '</span>' +
'<select disabled=""></select>' +
'<a href="#" class="list-remove one-button talk-images" title="' + _e("Remove") + '"></a>' +
'</div>' +
'<div class="list-center"></div>' +
'<div class="list-right">' +
'<span>' + _e("Add") + '</span>' +
'<input type="text" placeholder="' + _e("List name") + '" />' +
'</div>' +
'</div>' +
'<div class="privacy-item">' +
'<span>' + _e("Item") + '</span>' +
'<select disabled=""></select>' +
'<a href="#" class="item-add one-button talk-images" title="' + _e("Add") + '"></a>' +
'<a href="#" class="item-remove one-button talk-images" title="' + _e("Remove") + '"></a>' +
'<a href="#" class="item-save one-button talk-images">' + _e("Save") + '</a>' +
'<div class="clear"></div>' +
'</div>' +
'<div class="privacy-form">' +
'<div class="privacy-first">' +
'<label><input type="radio" name="action" value="allow" disabled="" />' + _e("Allow") + '</label>' +
'<label><input type="radio" name="action" value="deny" disabled="" />' + _e("Deny") + '</label>' +
'</div>' +
'<div class="privacy-second">' +
'<label><input type="radio" name="type" value="jid" disabled="" />' + _e("Address") + '</label>' +
'<input type="text" name="jid" disabled="" />' +
'<label><input type="radio" name="type" value="group" disabled="" />' + _e("Group") + '</label>' +
'<select name="group" disabled="">' + groupsToHtmlPrivacy() + '</select>' +
'<label><input type="radio" name="type" value="subscription" disabled="" />' + _e("Subscription") + '</label>' +
'<select name="subscription" disabled="">' +
'<option value="none">' + _e("None") + '</option>' +
'<option value="both">' + _e("Both") + '</option>' +
'<option value="from">' + _e("From") + '</option>' +
'<option value="to">' + _e("To") + '</option>' +
'</select>' +
'<label><input type="radio" name="type" value="everybody" disabled="" />' + _e("Everybody") + '</label>' +
'</div>' +
'<div class="privacy-third">' +
'<label><input type="checkbox" name="send-messages" disabled="" />' + _e("Send messages") + '</label>' +
'<label><input type="checkbox" name="send-queries" disabled="" />' + _e("Send queries") + '</label>' +
'<label><input type="checkbox" name="see-status" disabled="" />' + _e("See my status") + '</label>' +
'<label><input type="checkbox" name="send-status" disabled="" />' + _e("Send his/her status") + '</label>' +
'<label><input type="checkbox" name="everything" disabled="" />' + _e("Everything") + '</label>' +
'</div>' +
'<div class="clear"></div>' +
'</div>' +
'<div class="privacy-active">' +
'<label>' + _e("Order") + '<input type="text" name="order" value="1" disabled="" /></label>' +
'<div class="privacy-active-elements">' +
'<label><input type="checkbox" name="active" disabled="" />' + _e("Active for this session") + '</label>' +
'<label><input type="checkbox" name="default" disabled="" />' + _e("Always active") + '</label>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('privacy', html);
// Associate the events
launchPrivacy();
// Display the available privacy lists
displayListsPrivacy();
// Get the first list items
displayItemsPrivacy();
return false;
}
// Quits the privacy popup
function closePrivacy() {
// Destroy the popup
destroyPopup('privacy');
return false;
}
// Sets the received state for privacy block list
function receivedPrivacy() {
// Store marker
setDB('privacy-marker', 'available', 'true');
// Show privacy elements
$('.privacy-hidable').show();
}
// Gets available privacy lists
function listPrivacy() {
// Build query
var iq = new JSJaCIQ();
iq.setType('get');
iq.setQuery(NS_PRIVACY);
con.send(iq, handleListPrivacy);
logThis('Getting available privacy list(s)...');
}
// Handles available privacy lists
function handleListPrivacy(iq) {
// Error?
if(iq.getType() == 'error')
return logThis('Privacy lists not supported!', 2);
// Get IQ query content
var iqQuery = iq.getQuery();
// Save the content
setDB('privacy-lists', 'available', xmlToString(iqQuery));
// Any block list?
if($(iqQuery).find('list[name="block"]').size()) {
// Not the default one?
if(!$(iqQuery).find('default[name="block"]').size())
changePrivacy('block', 'default');
else
setDB('privacy-marker', 'default', 'block');
// Not the active one?
if(!$(iqQuery).find('active[name="block"]').size())
changePrivacy('block', 'active');
else
setDB('privacy-marker', 'active', 'block');
// Get the block list rules
getPrivacy('block');
}
// Apply the received marker here
else
receivedPrivacy();
logThis('Got available privacy list(s).', 3);
}
// Gets privacy lists
function getPrivacy(list) {
// Build query
var iq = new JSJaCIQ();
iq.setType('get');
// Privacy query
var iqQuery = iq.setQuery(NS_PRIVACY);
iqQuery.appendChild(iq.buildNode('list', {'xmlns': NS_PRIVACY, 'name': list}));
con.send(iq, handleGetPrivacy);
// Must show the wait item?
if(exists('#privacy'))
$('#privacy .wait').show();
logThis('Getting privacy list(s): ' + list);
}
// Handles privacy lists
function handleGetPrivacy(iq) {
// Apply a "received" marker
receivedPrivacy();
// Store the data for each list
$(iq.getQuery()).find('list').each(function() {
// Read list name
var list_name = $(this).attr('name');
// Store list content
setDB('privacy', list_name, xmlToString(this));
// Is this a block list?
if(list_name == 'block') {
// Reset buddies
$('#buddy-list .buddy').removeClass('blocked');
// XID types
$(this).find('item[action="deny"][type="jid"]').each(function() {
$('#buddy-list .buddy[data-xid="' + escape($(this).attr('value')) + '"]').addClass('blocked');
});
// Group types
$(this).find('item[action="deny"][type="group"]').each(function() {
$('#buddy-list .group' + hex_md5($(this).attr('value')) + ' .buddy').addClass('blocked');
});
}
});
// Must display it to the popup?
if(exists('#privacy')) {
displayItemsPrivacy();
$('#privacy .wait').hide();
}
logThis('Got privacy list(s).', 3);
}
// Sets a privacy list
function setPrivacy(list, types, values, actions, orders, presence_in, presence_out, msg, iq_p) {
// Build query
var iq = new JSJaCIQ();
iq.setType('set');
// Privacy query
var iqQuery = iq.setQuery(NS_PRIVACY);
var iqList = iqQuery.appendChild(iq.buildNode('list', {'xmlns': NS_PRIVACY, 'name': list}));
// Build the item elements
if(types && types.length) {
for(var i = 0; i < types.length; i++) {
// Item element
var iqItem = iqList.appendChild(iq.buildNode('item', {'xmlns': NS_PRIVACY}));
// Item attributes
if(types[i])
iqItem.setAttribute('type', types[i]);
if(values[i])
iqItem.setAttribute('value', values[i]);
if(actions[i])
iqItem.setAttribute('action', actions[i]);
if(orders[i])
iqItem.setAttribute('order', orders[i]);
// Child elements
if(presence_in[i])
iqItem.appendChild(iq.buildNode('presence-in', {'xmlns': NS_PRIVACY}));
if(presence_out[i])
iqItem.appendChild(iq.buildNode('presence-out', {'xmlns': NS_PRIVACY}));
if(msg[i])
iqItem.appendChild(iq.buildNode('message', {'xmlns': NS_PRIVACY}));
if(iq_p[i])
iqItem.appendChild(iq.buildNode('iq', {'xmlns': NS_PRIVACY}));
}
}
con.send(iq, function(iq) {
if(iq.getType() == 'result')
logThis('Sent privacy list.');
else
logThis('Error sending privacy list.', 1);
});
logThis('Sending privacy list: ' + list);
}
// Push a privacy list item to a list
function pushPrivacy(list, type, value, action, presence_in, presence_out, msg, iq_p, hash, special_action) {
// Read the stored elements (to add them)
var stored = XMLFromString(getDB('privacy', list));
// Read the first value
var first_val = value[0];
// Order generation flow
var order = [];
var highest_order = 0;
// Must remove the given value?
if(special_action == 'remove') {
type = [];
value = [];
action = [];
presence_in = [];
presence_out = [];
iq_p = [];
}
// Serialize them to an array
$(stored).find('item').each(function() {
// Attributes
var c_type = $(this).attr('type');
var c_value = $(this).attr('value');
var c_action = $(this).attr('action');
var c_order = $(this).attr('order');
// Generate hash
var c_hash = hex_md5(c_type + c_value);
// Do not push it twice!
if(((c_hash != hash) && (special_action != 'roster')) || ((first_val != c_value) && (special_action == 'roster'))) {
if(!c_type)
c_type = '';
if(!c_value)
c_value = '';
if(!c_action)
c_action = '';
if(!c_order)
c_order = '';
if(!isNaN(c_order) && parseInt(c_order) > highest_order)
highest_order = parseInt(c_order);
type.push(c_type);
value.push(c_value);
action.push(c_action);
order.push(c_order);
// Child elements
if($(this).find('presence-in').size())
presence_in.push(true);
else
presence_in.push(false);
if($(this).find('presence-out').size())
presence_out.push(true);
else
presence_out.push(false);
if($(this).find('message').size())
msg.push(true);
else
msg.push(false);
if($(this).find('iq').size())
iq_p.push(true);
else
iq_p.push(false);
}
});
order.unshift((++highest_order) + '');
// Send it!
setPrivacy(list, type, value, action, order, presence_in, presence_out, msg, iq_p);
}
// Change a privacy list status
function changePrivacy(list, status) {
// Yet sent?
if(getDB('privacy-marker', status) == list)
return;
// Write a marker
setDB('privacy-marker', status, list);
// Build query
var iq = new JSJaCIQ();
iq.setType('set');
// Privacy query
var iqQuery = iq.setQuery(NS_PRIVACY);
var iqStatus = iqQuery.appendChild(iq.buildNode(status, {'xmlns': NS_PRIVACY}));
// Can add a "name" attribute?
if(list)
iqStatus.setAttribute('name', list);
con.send(iq);
logThis('Changing privacy list status: ' + list + ' to: ' + status);
}
// Checks the privacy status (action) of a value
function statusPrivacy(list, value) {
return $(XMLFromString(getDB('privacy', list))).find('item[value="' + value + '"]').attr('action');
}
// Converts the groups array into a <option /> string
function groupsToHtmlPrivacy() {
var groups = getAllGroups();
var html = '';
// Generate HTML
for(i in groups) {
html += '<option value="' + encodeQuotes(groups[i]) +'">' + groups[i].htmlEnc() + '</option>';
}
return html;
}
// Displays the privacy lists
function displayListsPrivacy() {
// Initialize
var code = '';
var select = $('#privacy .privacy-head .list-left select');
var data = XMLFromString(getDB('privacy-lists', 'available'));
// Parse the XML data!
$(data).find('list').each(function() {
var list_name = $(this).attr('name');
if(list_name)
code += '<option value="' + encodeQuotes(list_name) + '">' + list_name.htmlEnc() + '</option>';
});
// Apply HTML code
select.html(code);
// Not empty?
if(code)
select.removeAttr('disabled');
else
select.attr('disabled', true);
return true;
}
// Displays the privacy items for a list
function displayItemsPrivacy() {
// Reset the form
clearFormPrivacy();
disableFormPrivacy();
// Initialize
var code = '';
var select = $('#privacy .privacy-item select');
var list = $('#privacy .privacy-head .list-left select').val();
// Reset the item select
select.html('');
// No list?
if(!list)
return false;
// Reset the list status
$('#privacy .privacy-active input[type="checkbox"]').removeAttr('checked');
// Display the list status
var status = ['active', 'default'];
for(s in status) {
if(getDB('privacy-marker', status[s]) == list)
$('#privacy .privacy-active input[name=' + status[s] + ']').attr('checked', true);
}
// Try to read the stored items
var items = XMLFromString(getDB('privacy', list));
// Must retrieve the data?
if(!items) {
select.attr('disabled', true);
return getPrivacy(list);
}
else
select.removeAttr('disabled');
// Parse the XML data!
$(items).find('item').each(function() {
// Read attributes
var item_type = $(this).attr('type');
var item_value = $(this).attr('value');
var item_action = $(this).attr('action');
var item_order = $(this).attr('order');
// Generate hash
var item_hash = hex_md5(item_type + item_value + item_action + item_order);
// Read sub-elements
var item_presencein = $(this).find('presence-in').size();
var item_presenceout = $(this).find('presence-out').size();
var item_message = $(this).find('message').size();
var item_iq = $(this).find('iq').size();
// Apply default values (if missing)
if(!item_type)
item_type = '';
if(!item_value)
item_value = '';
if(!item_action)
item_action = 'allow';
if(!item_order)
item_order = '1';
// Apply sub-elements values
if(item_presencein)
item_presencein = 'true';
else
item_presencein = 'false';
if(item_presenceout)
item_presenceout = 'true';
else
item_presenceout = 'false';
if(item_message)
item_message = 'true';
else
item_message = 'false';
if(item_iq)
item_iq = 'true';
else
item_iq = 'false';
// Generate item description
var desc = '';
var desc_arr = [item_type, item_value, item_action, item_order];
for(d in desc_arr) {
// Nothing to display?
if(!desc_arr[d])
continue;
if(desc)
desc += ' - ';
desc += desc_arr[d];
}
// Add the select option
code += '<option data-type="' + encodeQuotes(item_type) + '" data-value="' + encodeQuotes(item_value) + '" data-action="' + encodeQuotes(item_action) + '" data-order="' + encodeQuotes(item_order) + '" data-presence_in="' + encodeQuotes(item_presencein) + '" data-presence_out="' + encodeQuotes(item_presenceout) + '" data-message="' + encodeQuotes(item_message) + '" data-iq="' + encodeQuotes(item_iq) + '" data-hash="' + encodeQuotes(item_hash) + '">' +
desc +
'</option>';
});
// Append the code
select.append(code);
// Display the first item form
var first_item = select.find('option:first');
displayFormPrivacy(
first_item.attr('data-type'),
first_item.attr('data-value'),
first_item.attr('data-action'),
first_item.attr('data-order'),
first_item.attr('data-presence_in'),
first_item.attr('data-presence_out'),
first_item.attr('data-message'),
first_item.attr('data-iq')
);
return true;
}
// Displays the privacy form for an item
function displayFormPrivacy(type, value, action, order, presence_in, presence_out, message, iq) {
// Reset the form
clearFormPrivacy();
// Apply the action
$('#privacy .privacy-first input[name="action"][value="' + action + '"]').attr('checked', true);
// Apply the type & value
var privacy_second = '#privacy .privacy-second';
var privacy_type = privacy_second + ' input[name="type"]';
var type_check, value_input;
switch(type) {
case 'jid':
type_check = privacy_type + '[value="jid"]';
value_input = privacy_second + ' input[type="text"][name="jid"]';
break;
case 'group':
type_check = privacy_type + '[value="group"]';
value_input = privacy_second + ' select[name="group"]';
break;
case 'subscription':
type_check = privacy_type + '[value="subscription"]';
value_input = privacy_second + ' select[name="subscription"]';
break;
default:
type_check = privacy_type + '[value="everybody"]';
break;
}
// Check the target
$(type_check).attr('checked', true);
// Can apply a value?
if(value_input)
$(value_input).val(value);
// Apply the things to do
var privacy_do = '#privacy .privacy-third input[type="checkbox"]';
if(presence_in == 'true')
$(privacy_do + '[name="send-status"]').attr('checked', true);
if(presence_out == 'true')
$(privacy_do + '[name="see-status"]').attr('checked', true);
if(message == 'true')
$(privacy_do + '[name="send-messages"]').attr('checked', true);
if(iq == 'true')
$(privacy_do + '[name="send-queries"]').attr('checked', true);
if(!$(privacy_do).filter(':checked').size())
$(privacy_do + '[name="everything"]').attr('checked', true);
// Apply the order
$('#privacy .privacy-active input[name="order"]').val(order);
// Enable the forms
$('#privacy .privacy-form input, #privacy .privacy-form select, #privacy .privacy-active input').removeAttr('disabled');
}
// Clears the privacy list form
function clearFormPrivacy() {
// Uncheck checkboxes & radio inputs
$('#privacy .privacy-form input[type="checkbox"], #privacy .privacy-form input[type="radio"]').removeAttr('checked');
// Reset select
$('#privacy .privacy-form select option').removeAttr('selected');
$('#privacy .privacy-form select option:first').attr('selected', true);
// Reset text input
$('#privacy .privacy-form input[type="text"]').val('');
// Reset order input
$('#privacy .privacy-active input[name="order"]').val('1');
}
// Disables the privacy list form
function disableFormPrivacy() {
$('#privacy .privacy-form input, #privacy .privacy-form select, #privacy .privacy-active input').attr('disabled', true);
}
// Enables the privacy list form
function enableFormPrivacy(rank) {
$('#privacy .privacy-' + rank + ' input, #privacy .privacy-' + rank + ' select').removeAttr('disabled');
}
// Plugin launcher
function launchPrivacy() {
// Click events
$('#privacy .bottom .finish').click(closePrivacy);
// Placeholder events
$('#privacy input[placeholder]').placeholder();
// Form events
$('#privacy .privacy-head a.list-remove').click(function() {
// Get list name
var list = $('#privacy .privacy-head .list-left select').val();
// No value?
if(!list)
return false;
// Remove it from popup
$('#privacy .privacy-head .list-left select option[value="' + list + '"]').remove();
// Nothing remaining?
if(!exists('#privacy .privacy-head .list-left select option'))
$('#privacy .privacy-head .list-left select option').attr('disabled', true);
// Empty the item select
$('#privacy .privacy-item select').attr('disabled', true).html('');
// Disable this list before removing it
var status = ['active', 'default'];
for(s in status) {
if(getDB('privacy-marker', status[s]) == list)
changePrivacy('', status[s]);
}
// Remove from server
setPrivacy(list);
// Reset the form
clearFormPrivacy();
disableFormPrivacy();
return false;
});
$('#privacy .privacy-head .list-right input').keyup(function(e) {
// Not enter?
if(e.keyCode != 13)
return;
// Get list name
var list = $('#privacy .privacy-head .list-right input').val();
var select = '#privacy .privacy-head .list-left select';
var existed = true;
// Create the new element
if(!exists(select + ' option[value="' + list + '"]')) {
// Marker
existed = false;
// Create a new option
$(select).append('<option value="' + encodeQuotes(list) + '">' + list.htmlEnc() + '</option>');
// Reset the item select
$('#privacy .privacy-item select').attr('disabled', true).html('');
}
// Change the select value & enable it
$(select).val(list).removeAttr('disabled');
// Reset its value
$(this).val('');
// Reset the form
clearFormPrivacy();
disableFormPrivacy();
// Must reload the list items?
if(existed) {
displayItemsPrivacy();
$('#privacy .privacy-item select').removeAttr('disabled');
}
});
$('#privacy .privacy-head .list-left select').change(displayItemsPrivacy);
$('#privacy .privacy-item select').change(function() {
// Get the selected item
var item = $(this).find('option:selected');
// Display the data!
displayFormPrivacy(
item.attr('data-type'),
item.attr('data-value'),
item.attr('data-action'),
item.attr('data-order'),
item.attr('data-presence_in'),
item.attr('data-presence_out'),
item.attr('data-message'),
item.attr('data-iq')
);
});
$('#privacy .privacy-item a.item-add').click(function() {
// Cannot add anything?
if(!exists('#privacy .privacy-head .list-left select option:selected'))
return false;
// Disable item select
$('#privacy .privacy-item select').attr('disabled', true);
// Reset the form
clearFormPrivacy();
disableFormPrivacy();
// Enable first form item
enableFormPrivacy('first');
enableFormPrivacy('active');
return false;
});
$('#privacy .privacy-item a.item-remove').click(function() {
// Cannot add anything?
if(!exists('#privacy .privacy-head .list-left select option:selected'))
return false;
// Get values
var list = $('#privacy .privacy-head .list-left select').val();
var selected = $('#privacy .privacy-item select option:selected');
var item = selected.attr('data-value');
var hash = selected.attr('data-hash');
// Remove it from popup
$('#privacy .privacy-item select option:selected').remove();
// No more items in this list?
if(!exists('#privacy .privacy-item select option')) {
// Disable this select
$('#privacy .privacy-item select').attr('disabled', true);
// Remove the privacy list select item
$('#privacy .privacy-head .list-left select option[value="' + list + '"]').remove();
// No more privacy lists?
if(!exists('#privacy .privacy-head .list-left select option'))
$('#privacy .privacy-head .list-left select').attr('disabled', true);
// Disable this list before removing it
var status = ['active', 'default'];
for(s in status) {
if(getDB('privacy-marker', status[s]) == list)
changePrivacy('', status[s]);
}
}
// Synchronize it with server
pushPrivacy(list, [], [item], [], [], [], [], [], [], hash, 'remove');
// Reset the form
clearFormPrivacy();
disableFormPrivacy();
return false;
});
$('#privacy .privacy-item a.item-save').click(function() {
// Canot push item?
if(exists('#privacy .privacy-form input:disabled'))
return false;
// Get the hash
var item_hash = '';
if(!$('#privacy .privacy-item select').is(':disabled'))
item_hash = $('#privacy .privacy-item select option:selected').attr('data-hash');
// Read the form
var privacy_second = '#privacy .privacy-second';
var item_list = $('#privacy .privacy-head .list-left select').val();
var item_action = $('#privacy .privacy-first input[name="action"]').filter(':checked').val();
var item_type = $(privacy_second + ' input[name="type"]').filter(':checked').val();
var item_order = $('#privacy .privacy-active input[name="order"]').val();
var item_value = '';
// Switch the type to get the value
switch(item_type) {
case 'jid':
item_value = $(privacy_second + ' input[type="text"][name="jid"]').val();
break;
case 'group':
item_value = $(privacy_second + ' select[name="group"]').val();
break;
case 'subscription':
item_value = $(privacy_second + ' select[name="subscription"]').val();
break;
default:
item_type = '';
break;
}
// Get the selected things to do
var privacy_third_cb = '#privacy .privacy-third input[type="checkbox"]';
var item_prin = false;
var item_prout = false;
var item_msg = false;
var item_iq = false;
// Individual select?
if(!$(privacy_third_cb + '[name="everything"]').filter(':checked').size()) {
if($(privacy_third_cb + '[name="send-messages"]').filter(':checked').size())
item_msg = true;
if($(privacy_third_cb + '[name="send-queries"]').filter(':checked').size())
item_iq = true;
if($(privacy_third_cb + '[name="send-queries"]').filter(':checked').size())
item_iq = true;
if($(privacy_third_cb + '[name="see-status"]').filter(':checked').size())
item_prout = true;
if($(privacy_third_cb + '[name="send-status"]').filter(':checked').size())
item_prin = true;
}
// Push item to the server!
pushPrivacy(
item_list,
[item_type],
[item_value],
[item_action],
[item_order],
[item_prin],
[item_prout],
[item_msg],
[item_iq],
item_hash
);
return false;
});
$('#privacy .privacy-first input').change(function() {
enableFormPrivacy('second');
});
$('#privacy .privacy-second input').change(function() {
enableFormPrivacy('third');
});
$('#privacy .privacy-third input[type="checkbox"]').change(function() {
// Target
var target = '#privacy .privacy-third input[type="checkbox"]';
// Must tick "everything" checkbox?
if(!$(target).filter(':checked').size())
$(target + '[name="everything"]').attr('checked', true);
// Must untick the other checkboxes?
else if($(this).is('[name="everything"]'))
$(target + ':not([name="everything"])').removeAttr('checked');
// Must untick "everything" checkbox?
else
$(target + '[name="everything"]').removeAttr('checked');
});
$('#privacy .privacy-active input[name="order"]').keyup(function() {
// Get the value
var value = $(this).val();
// No value?
if(!value)
return;
// Not a number?
if(isNaN(value))
value = 1;
else
value = parseInt(value);
// Negative?
if(value < 0)
value = value * -1;
// Apply the filtered value
$(this).val(value);
})
.blur(function() {
// No value?
if(!$(this).val())
$(this).val('1');
});
$('#privacy .privacy-active .privacy-active-elements input').change(function() {
// Get the values
var list_name = $('#privacy .privacy-head .list-left select').val();
var state_name = $(this).attr('name');
// Cannot continue?
if(!list_name || !state_name)
return;
// Change the current list status
if($(this).filter(':checked').size())
changePrivacy(list_name, state_name);
else
changePrivacy('', state_name);
});
}
/*
Jappix - An open social platform
These are the error functions for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 02/04/11
*/
// Shows the given error output
function showError(condition, reason, type) {
// Enough data to output the error
if(condition || reason) {
// Initialize the error text
var eText = '';
// Any error condition
if(condition)
eText += condition;
// Any error type
if(type && eText)
eText += ' (' + type + ')';
// Any error reason
if(reason) {
if(eText)
eText += ' - ';
eText += reason;
}
// We reveal the error
openThisError(1);
// Create the error text
$('#board .one-board.error[data-id="1"] span').text(eText);
}
// Not enough data to output the error: output a generic board
else
openThisError(2);
}
// Handles the error from a packet and return true if any error
function handleError(packet) {
/* REF: http://xmpp.org/extensions/xep-0086.html */
// Initialize
var type, code, reason, condition;
var node = $(packet);
// First level error (connection error)
if(node.is('error')) {
// Get the value
code = node.attr('code');
// Specific error reason
switch(code) {
case '401':
reason = _e("Authorization failed");
break;
case '409':
reason = _e("Registration failed, please choose a different username");
break;
case '503':
reason = _e("Service unavailable");
break;
case '500':
reason = _e("Internal server error, try later");
break;
default:
reason = node.find('text').text();
break;
}
// Remove the general wait item (security)
removeGeneralWait();
// Show reconnect pane
if(CURRENT_SESSION && CONNECTED) {
// Anonymous?
if(isAnonymous())
createReconnect('anonymous');
else
createReconnect('normal');
}
// Show the homepage (security)
else if(!CURRENT_SESSION || !CONNECTED) {
$('#home').show();
pageTitle('home');
}
// Still connected? (security)
if(isConnected())
con.disconnect();
logThis('First level error received.', 1);
}
// Second level error (another error)
else if(node.find('error').size()) {
type = node.find('error').attr('type');
reason = node.find('error text').text();
condition = packet.getElementsByTagName('error').item(0).childNodes.item(0).nodeName.replace(/-/g, ' ');
logThis('Second level error received.', 1);
}
// No error
else
return false;
// Show the error board
showError(condition, reason, type);
// Return there's an error
return true;
}
// Handles the error reply of a packet
function handleErrorReply(packet) {
return handleError(packet.getNode());
}
// Handles the error reply for a message
function handleMessageError(packet) {
if(!handleErrorReply(packet))
handleMessage(packet);
}
/*
Jappix - An open social platform
These are the buddy name related JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 29/04/11
*/
// Gets an user name for buddy add tool
function getAddUserName(xid) {
var iq = new JSJaCIQ();
iq.setType('get');
iq.setTo(xid);
iq.appendNode('vCard', {'xmlns': NS_VCARD});
con.send(iq, handleAddUserName);
}
// Handles an user name for buddy add tool
function handleAddUserName(iq) {
// Was it an obsolete request?
if(!exists('.add-contact-name-get[data-for="' + escape(bareXID(getStanzaFrom(iq))) + '"]'))
return false;
// Reset the waiting item
$('.add-contact-name-get').hide().removeAttr('data-for');
// Get the names
if(iq.getType() == 'result') {
var full_name = generateBuddyName(iq)[0];
if(full_name)
$('.add-contact-name').val(full_name);
}
return false;
}
// Generates the good buddy name from a vCard IQ reply
function generateBuddyName(iq) {
// Get the IQ content
var xml = $(iq.getNode()).find('vCard');
// Get the full name & the nickname
var pFull = xml.find('FN:first').text();
var pNick = xml.find('NICKNAME:first').text();
// No full name?
if(!pFull) {
// Get the given name
var pN = xml.find('N:first');
var pGiven = pN.find('GIVEN:first').text();
if(pGiven) {
pFull = pGiven;
// Get the family name (optional)
var pFamily = pN.find('FAMILY:first').text();
if(pFamily)
pFull += ' ' + pFamily;
}
}
return [pFull, pNick];
}
// Returns the given XID buddy name
function getBuddyName(xid) {
// Initialize
var cname, bname;
// Cut the XID resource
xid = bareXID(xid);
// This is me?
if(isAnonymous() && !xid)
bname = _e("You");
else if(xid == getXID())
bname = getName();
// Not me!
else {
cname = $('#buddy-list .buddy[data-xid="' + escape(xid) + '"]:first .buddy-name').html();
// If the complete name exists
if(cname)
bname = cname.revertHtmlEnc();
// Else, we just get the nickname of the buddy
else
bname = getXIDNick(xid);
}
return bname;
}
// Gets the nickname of the user
function getNick() {
try {
// Try to read the user nickname
var nick = getDB('profile', 'nick');
// No nick?
if(!nick)
nick = con.username;
return nick;
} catch(e) {}
}
// Gets the full name of the user
function getName() {
// Try to read the user name
var name = getDB('profile', 'name');
// No name? Use the nickname instead!
if(!name)
name = getNick();
return name;
}
// Gets the MUC nickname of the user
function getMUCNick(id) {
return unescape($('#' + id).attr('data-nick'));
}
/*
Jappix - An open social platform
These are the favorites JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 14/05/13
*/
// Opens the favorites popup
function openFavorites() {
// Popup HTML content
var html =
'<div class="top">' + _e("Manage favorite rooms") + '</div>' +
'<div class="content">' +
'<div class="switch-fav">' +
'<div class="room-switcher room-list">' +
'<div class="icon list-icon talk-images"></div>' +
_e("Change favorites") +
'</div>' +
'<div class="room-switcher room-search">' +
'<div class="icon search-icon talk-images"></div>' +
_e("Search a room") +
'</div>' +
'</div>' +
'<div class="static-fav">' +
'<div class="favorites-edit favorites-content">' +
'<div class="head fedit-head static-fav-head">' +
'<div class="head-text fedit-head-text">' + _e("Select a favorite") + '</div>' +
'<select name="fedit-head-select" class="head-select fedit-head-select"></select>' +
'</div>' +
'<div class="results fedit-results static-fav-results">' +
'<div class="fedit-line">' +
'<label>' + _e("Name") + '</label>' +
'<input class="fedit-title" type="text" required="" />' +
'</div>' +
'<div class="fedit-line">' +
'<label>' + _e("Nickname") + '</label>' +
'<input class="fedit-nick" type="text" value="' + getNick() + '" required="" />' +
'</div>' +
'<div class="fedit-line">' +
'<label>' + _e("Room") + '</label>' +
'<input class="fedit-chan" type="text" required="" />' +
'</div>' +
'<div class="fedit-line">' +
'<label>' + _e("Server") + '</label>' +
'<input class="fedit-server" type="text" value="' + HOST_MUC + '" required="" />' +
'</div>' +
'<div class="fedit-line">' +
'<label>' + _e("Password") + '</label>' +
'<input class="fedit-password" type="password" />' +
'</div>' +
'<div class="fedit-line">' +
'<label>' + _e("Automatic") + '</label>' +
'<input type="checkbox" class="fedit-autojoin" />' +
'</div>' +
'<div class="fedit-actions">' +
'<a href="#" class="fedit-terminate fedit-add add one-button talk-images">' + _e("Add") + '</a>' +
'<a href="#" class="fedit-terminate fedit-edit one-button talk-images">' + _e("Edit") + '</a>' +
'<a href="#" class="fedit-terminate fedit-remove remove one-button talk-images">' + _e("Remove") + '</a>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="favorites-search favorites-content">' +
'<div class="head fsearch-head static-fav-head">' +
'<div class="head-text fsearch-head-text">' + _e("Search a room on") + '</div>' +
'<input type="text" class="head-input fsearch-head-server" value="' + HOST_MUC + '" />' +
'</div>' +
'<div class="results fsearch-results static-fav-results">' +
'<p class="fsearch-noresults">' + _e("No room found on this server.") + '</p>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('favorites', html);
// Load the favorites
loadFavorites();
// Associate the events
launchFavorites();
}
// Resets the favorites elements
function resetFavorites() {
var path = '#favorites ';
$(path + '.wait, ' + path + '.fedit-terminate').hide();
$(path + '.fedit-add').show();
$(path + '.fsearch-oneresult').remove();
$(path + 'input').val('');
$(path + '.please-complete').removeClass('please-complete');
$(path + '.fedit-nick').val(getNick());
$(path + '.fsearch-head-server, ' + path + '.fedit-server').val(HOST_MUC);
$(path + '.fedit-autojoin').removeAttr('checked');
}
// Quits the favorites popup
function quitFavorites() {
// Destroy the popup
destroyPopup('favorites');
return false;
}
// Adds a room to the favorites
function addThisFavorite(roomXID, roomName) {
// Button path
var button = '#favorites .fsearch-results div[data-xid="' + escape(roomXID) + '"] a.one-button';
// Add a remove button instead of the add one
$(button + '.add').replaceWith('<a href="#" class="one-button remove talk-images">' + _e("Remove") + '</a>');
// Click event
$(button + '.remove').click(function() {
return removeThisFavorite(roomXID, roomName);
});
// Hide the add button in the (opened?) groupchat
$('#' + hex_md5(roomXID) + ' .tools-add').hide();
// Add the database entry
displayFavorites(roomXID, explodeThis(' (', roomName, 0), getNick(), '0', '');
// Publish the favorites
favoritePublish();
return false;
}
// Removes a room from the favorites
function removeThisFavorite(roomXID, roomName) {
// Button path
var button = '#favorites .fsearch-results div[data-xid="' + escape(roomXID) + '"] a.one-button';
// Add a remove button instead of the add one
$(button + '.remove').replaceWith('<a href="#" class="one-button add talk-images">' + _e("Add") + '</a>');
// Click event
$(button + '.add').click(function() {
return addThisFavorite(roomXID, roomName);
});
// Show the add button in the (opened?) groupchat
$('#' + hex_md5(roomXID) + ' .tools-add').show();
// Remove the favorite
removeFavorite(roomXID, true);
// Publish the favorites
favoritePublish();
return false;
}
// Edits a favorite
function editFavorite() {
// Path to favorites
var favorites = '#favorites .';
// Reset the favorites
resetFavorites();
// Show the edit/remove button, hide the others
$(favorites + 'fedit-terminate').hide();
$(favorites + 'fedit-edit').show();
$(favorites + 'fedit-remove').show();
// We retrieve the values
var xid = $(favorites + 'fedit-head-select').val();
var data = XMLFromString(getDB('favorites', xid));
// If this is not the default room
if(xid != 'none') {
// We apply the values
$(favorites + 'fedit-title').val($(data).find('name').text());
$(favorites + 'fedit-nick').val($(data).find('nick').text());
$(favorites + 'fedit-chan').val(getXIDNick(xid));
$(favorites + 'fedit-server').val(getXIDHost(xid));
$(favorites + 'fedit-password').val($(data).find('password').text());
if($(data).find('autojoin').text() == 'true')
$(favorites + 'fedit-autojoin').attr('checked', true);
}
}
// Adds a favorite
function addFavorite() {
// Path to favorites
var favorites = '#favorites .';
// We reset the inputs
$(favorites + 'fedit-title, ' + favorites + 'fedit-nick, ' + favorites + 'fedit-chan, ' + favorites + 'fedit-server, ' + favorites + 'fedit-password').val('');
// Show the add button, hide the others
$(favorites + 'fedit-terminate').hide();
$(favorites + 'fedit-add').show();
}
// Terminate a favorite editing
function terminateThisFavorite(type) {
// Path to favorites
var favorites = '#favorites ';
// We get the values of the current edited groupchat
var old_xid = $(favorites + '.fedit-head-select').val();
var title = $(favorites + '.fedit-title').val();
var nick = $(favorites + '.fedit-nick').val();
var room = $(favorites + '.fedit-chan').val();
var server = $(favorites + '.fedit-server').val();
var xid = room + '@' + server;
var password = $(favorites + '.fedit-password').val();
var autojoin = 'false';
if($(favorites + '.fedit-autojoin').filter(':checked').size())
autojoin = 'true';
// We check the missing values and send this if okay
if((type == 'add') || (type == 'edit')) {
if(title && nick && room && server) {
// Remove the edited room
if(type == 'edit')
removeFavorite(old_xid, true);
// Display the favorites
displayFavorites(xid, title, nick, autojoin, password);
// Reset the inputs
resetFavorites();
}
else {
$(favorites + 'input[required]').each(function() {
var select = $(this);
if(!select.val())
$(document).oneTime(10, function() {
select.addClass('please-complete').focus();
});
else
select.removeClass('please-complete');
});
}
}
// Must remove a favorite?
else if(type == 'remove') {
removeFavorite(old_xid, true);
// Reset the inputs
resetFavorites();
}
// Publish the new favorites
favoritePublish();
logThis('Action on this bookmark: ' + room + '@' + server + ' / ' + type, 3);
return false;
}
// Removes a favorite
function removeFavorite(xid, database) {
// We remove the target favorite everywhere needed
$('.buddy-conf-groupchat-select option[value="' + xid + '"]').remove();
$('.fedit-head-select option[value="' + xid + '"]').remove();
// Must remove it from database?
if(database)
removeDB('favorites', xid);
}
// Sends a favorite to the XMPP server
function favoritePublish() {
var iq = new JSJaCIQ();
iq.setType('set');
var query = iq.setQuery(NS_PRIVATE);
var storage = query.appendChild(iq.buildNode('storage', {'xmlns': NS_BOOKMARKS}));
// We generate the XML
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored favorite
if(explodeThis('_', current, 0) == 'favorites') {
var data = XMLFromString(storageDB.getItem(current));
var xid = $(data).find('xid').text();
var rName = $(data).find('name').text();
var nick = $(data).find('nick').text();
var password = $(data).find('password').text();
var autojoin = $(data).find('autojoin').text();
// We create the node for this groupchat
var item = storage.appendChild(iq.buildNode('conference', {'name': rName, 'jid': xid, 'autojoin': autojoin, xmlns: NS_BOOKMARKS}));
item.appendChild(iq.buildNode('nick', {xmlns: NS_BOOKMARKS}, nick));
if(password)
item.appendChild(iq.buildNode('password', {xmlns: NS_BOOKMARKS}, password));
logThis('Bookmark sent: ' + xid, 3);
}
}
con.send(iq);
}
// Gets a list of the MUC items on a given server
function getGCList() {
var path = '#favorites .';
var gcServer = $('.fsearch-head-server').val();
// We reset some things
$(path + 'fsearch-oneresult').remove();
$(path + 'fsearch-noresults').hide();
$(path + 'wait').show();
var iq = new JSJaCIQ();
iq.setType('get');
iq.setTo(gcServer);
iq.setQuery(NS_DISCO_ITEMS);
con.send(iq, handleGCList);
}
// Handles the MUC items list
function handleGCList(iq) {
var path = '#favorites .';
var from = fullXID(getStanzaFrom(iq));
if (!iq || (iq.getType() != 'result')) {
openThisError(3);
$(path + 'wait').hide();
logThis('Error while retrieving the rooms: ' + from, 1);
}
else {
var handleXML = iq.getQuery();
if($(handleXML).find('item').size()) {
// Initialize the HTML code
var html = '';
$(handleXML).find('item').each(function() {
var roomXID = $(this).attr('jid');
var roomName = $(this).attr('name');
if(roomXID && roomName) {
// Escaped values
var escaped_xid = encodeOnclick(roomXID);
var escaped_name = encodeOnclick(roomName);
// Initialize the room HTML
html += '<div class="oneresult fsearch-oneresult" data-xid="' + escape(roomXID) + '">' +
'<div class="room-name">' + roomName.htmlEnc() + '</div>' +
'<a href="#" class="one-button join talk-images" onclick="return joinFavorite(\'' + escaped_xid + '\');">' + _e("Join") + '</a>';
// This room is yet a favorite
if(existDB('favorites', roomXID))
html += '<a href="#" class="one-button remove talk-images" onclick="return removeThisFavorite(\'' + escaped_xid + '\', \'' + escaped_name + '\');">' + _e("Remove") + '</a>';
else
html += '<a href="#" class="one-button add talk-images" onclick="return addThisFavorite(\'' + escaped_xid + '\', \'' + escaped_name + '\');">' + _e("Add") + '</a>';
// Close the room HTML
html += '</div>';
}
});
// Append this code to the popup
$(path + 'fsearch-results').append(html);
}
else
$(path + 'fsearch-noresults').show();
logThis('Rooms retrieved: ' + from, 3);
}
$(path + 'wait').hide();
}
// Joins a groupchat from favorites
function joinFavorite(room) {
quitFavorites();
checkChatCreate(room, 'groupchat', '', '', getXIDNick(room));
return false;
}
// Displays a given favorite
function displayFavorites(xid, name, nick, autojoin, password) {
// Generate the HTML code
var html = '<option value="' + encodeQuotes(xid) + '">' + name.htmlEnc() + '</option>';
// Remove the existing favorite
removeFavorite(xid, false);
// We complete the select forms
$('#buddy-list .gc-join-first-option, #favorites .fedit-head-select-first-option').after(html);
// We store the informations
var value = '<groupchat><xid>' + xid.htmlEnc() + '</xid><name>' + name.htmlEnc() + '</name><nick>' + nick.htmlEnc() + '</nick><autojoin>' + autojoin.htmlEnc() + '</autojoin><password>' + password.htmlEnc() + '</password></groupchat>';
setDB('favorites', xid, value);
}
// Loads the favorites for the popup
function loadFavorites() {
// Initialize the HTML code
var html = '';
// Read the database
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored favorite
if(explodeThis('_', current, 0) == 'favorites') {
var data = XMLFromString(storageDB.getItem(current));
// Add the current favorite to the HTML code
html += '<option value="' + encodeQuotes($(data).find('xid').text()) + '">' + $(data).find('name').text().htmlEnc() + '</option>';
}
}
// Generate specific HTML code
var favorites_bubble = '<option value="none" class="gc-join-first-option" selected="">' + _e("Select a favorite") + '</option>' + html;
var favorites_popup = '<option value="none" class="fedit-head-select-first-option" selected="">' + _e("Select a favorite") + '</option>' + html;
// Append the HTML code
$('#buddy-list .buddy-conf-groupchat-select').html(favorites_bubble);
$('#favorites .fedit-head-select').html(favorites_popup);
}
// Plugin launcher
function launchFavorites() {
var path = '#favorites .';
// Keyboard events
$(path + 'fsearch-head-server').keyup(function(e) {
if(e.keyCode == 13) {
// No value?
if(!$(this).val())
$(this).val(HOST_MUC);
// Get the list
getGCList();
}
});
$(path + 'fedit-line input').keyup(function(e) {
if(e.keyCode == 13) {
// Edit a favorite
if($(path + 'fedit-edit').is(':visible'))
terminateThisFavorite('edit');
// Add a favorite
else
terminateThisFavorite('add');
}
});
// Change events
$('.fedit-head-select').change(editFavorite);
// Click events
$(path + 'room-switcher').click(function() {
$(path + 'favorites-content').hide();
resetFavorites();
});
$(path + 'room-list').click(function() {
$(path + 'favorites-edit').show();
});
$(path + 'room-search').click(function() {
$(path + 'favorites-search').show();
getGCList();
});
$(path + 'fedit-add').click(function() {
return terminateThisFavorite('add');
});
$(path + 'fedit-edit').click(function() {
return terminateThisFavorite('edit');
});
$(path + 'fedit-remove').click(function() {
return terminateThisFavorite('remove');
});
$(path + 'bottom .finish').click(function() {
return quitFavorites();
});
}
/*
Jappix - An open social platform
This is the server features JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou, Maranda
Last revision: 07/06/13
*/
// Gets the features of a server
function getFeatures() {
/* REF: http://xmpp.org/extensions/xep-0030.html */
// Get the main values
var to = getServer();
var caps = con.server_caps;
var xml = null;
// Try to get the stored data
if(caps)
xml = XMLFromString(getPersistent('global', 'caps', caps));
// Any stored data?
if(xml) {
handleFeatures(xml);
logThis('Read server CAPS from cache.');
}
// Not stored (or no CAPS)!
else {
var iq = new JSJaCIQ();
iq.setTo(to);
iq.setType('get');
iq.setQuery(NS_DISCO_INFO);
con.send(iq, handleDiscoInfos);
logThis('Read server CAPS from network.');
}
}
// Handles the features of a server
function handleFeatures(xml) {
// Selector
var selector = $(xml);
// Markers
var pep = false;
var pubsub = false;
var pubsub_cn = false;
var mam = false;
var commands = false;
// Scan the features
if(selector.find('identity[category="pubsub"][type="pep"]').size())
pep = true;
if(selector.find('feature[var="' + NS_PUBSUB + '"]').size())
pubsub = true;
if(selector.find('feature[var="' + NS_PUBSUB_CN + '"]').size())
pubsub_cn = true;
if(selector.find('feature[var="' + NS_URN_MAM + '"]').size())
mam = true;
if(selector.find('feature[var="' + NS_COMMANDS + '"]').size())
commands = true;
// Enable the pep elements if available
if(pep) {
// Update our database
enableFeature('pep');
// Get the PEP nodes to initiate
getInitMicroblog();
getInitGeoloc();
// Get the notifications
getNotifications();
// Geolocate the user
geolocate();
// Enable microblogging send tools
waitMicroblog('sync');
$('.postit.attach').css('display', 'block');
logThis('XMPP server supports PEP.', 3);
}
// Disable microblogging send tools (no PEP!)
else {
waitMicroblog('unsync');
logThis('XMPP server does not support PEP.', 2);
}
// Enable the pubsub features if available
if(pubsub)
enableFeature(NS_PUBSUB);
// Enable the pubsub config-node features if available
if(pubsub_cn)
enableFeature(NS_PUBSUB_CN);
// Enable the message MAM management features if available
if(mam)
enableFeature(NS_URN_MAM);
// Enable the commands features if available
if(commands)
enableFeature(NS_COMMANDS);
// Hide the private life fieldset if nothing to show
if(!pep && !mam)
$('#options fieldset.privacy').hide();
// Apply the features
applyFeatures('talk');
// Process the buddy-list height
if(pep)
adaptRoster();
return false;
}
// The function to apply the features to an element
function applyFeatures(id) {
// Path to the elements
var path = '#' + id + ' .';
// PEP features
if(enabledPEP())
$(path + 'pep-hidable').show();
// PubSub features
if(enabledPubSub())
$(path + 'pubsub-hidable').show();
// PubSub Config-Node features
if(enabledPubSubCN())
$(path + 'pubsub-hidable-cn').show();
// MAM features
if(enabledMAM())
$(path + 'mam-hidable').show();
// Commands features
if(enabledCommands())
$(path + 'commands-hidable').show();
// XMPP links (browser feature)
if(navigator.registerProtocolHandler)
$(path + 'xmpplinks-hidable').show();
}
// Enables a feature
function enableFeature(feature) {
setDB('feature', feature, 'true');
}
// Checks if a feature is enabled
function enabledFeature(feature) {
if(getDB('feature', feature) == 'true')
return true;
else
return false;
}
// Returns the XMPP server PEP support
function enabledPEP() {
return enabledFeature('pep');
}
// Returns the XMPP server PubSub support
function enabledPubSub() {
return enabledFeature(NS_PUBSUB);
}
// Returns the XMPP server PubSub Config-Node support
function enabledPubSubCN() {
return enabledFeature(NS_PUBSUB_CN);
}
// Returns the XMPP server MAM support
function enabledMAM() {
return enabledFeature(NS_URN_MAM);
}
// Returns the XMPP server commands support
function enabledCommands() {
return enabledFeature(NS_COMMANDS);
}
/*
Jappix - An open social platform
These are the interface JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 23/01/13
*/
// Changes the title of the document
function pageTitle(title) {
// Anonymous mode?
var head_name = getName();
if(isAnonymous())
head_name = ANONYMOUS_ROOM + ' (' + _e("anonymous mode") + ')';
// We change the title to give essential informations
switch(title) {
case 'home':
document.title = SERVICE_NAME + ' • ' + SERVICE_DESC;
break;
case 'talk':
document.title = SERVICE_NAME + ' • ' + head_name;
break;
case 'new':
document.title = '[' + pendingEvents() + '] ' + SERVICE_NAME + ' • ' + head_name;
break;
case 'wait':
document.title = SERVICE_NAME + ' • ' + _e("Please wait...");
break;
}
}
// Creates a general-wait item
function showGeneralWait() {
// Item exists?
if(exists('#general-wait'))
return false;
// Generate the HTML code
var html =
'<div id="general-wait" class="removable">' +
'<div class="general-wait-content wait-big"></div>' +
'</div>';
// Append the HTML code
$('body').append(html);
return true;
}
// Removes the general-wait item
function removeGeneralWait() {
$('#general-wait').remove();
}
// Generates a file upload valid form content
function generateFileShare() {
return '<input type="hidden" name="MAX_FILE_SIZE" value="' + encodeQuotes(JAPPIX_MAX_FILE_SIZE) + '">' +
'<input type="hidden" name="user" value="' + encodeQuotes(getXID()) + '" />' +
'<input type="hidden" name="location" value="' + encodeQuotes(generateURL(JAPPIX_LOCATION)) + '" />' +
'<input type="hidden" name="id" value="' + (new Date()).getTime() + '" />' +
'<input type="file" name="file" required="" />' +
'<input type="submit" value="' + _e("Send") + '" />';
}
// Switches to the given chan
function switchChan(id) {
if(exists('#' + id)) {
// We show the page-engine content
$('.page-engine-chan').hide();
$('#' + id).show();
// We edit the tab switcher
$('#page-switch .switcher').removeClass('activechan').addClass('chan');
$('#page-switch .' + id).addClass('activechan').removeClass('chan');
// Scroll down to the last message
if(id != 'channel')
autoScroll(id);
// Manage input focus
inputFocus();
}
return false;
}
// Loads the complete chat switcher
function loadChatSwitch() {
// Path
var more_content = '#page-switch .more-content';
// Yet displayed?
if(exists(more_content))
return closeBubbles();
// Add the bubble
showBubble(more_content);
// Append the content
$('#page-switch .more').append(
'<div class="more-content bubble removable">' +
$('#page-switch .chans').html() +
'</div>'
);
return false;
}
// Puts the selected smiley in the good page-engine input
function insertSmiley(smiley, hash) {
// We define the variables
var selector = $('#' + hash + ' .message-area');
var oValue = selector.val();
// Any old value?
if(oValue && !oValue.match(/^(.+)(\s)+$/))
oValue += ' ';
var nValue = oValue + smiley + ' ';
// Put the new value and focus on it
$(document).oneTime(10, function() {
selector.val(nValue).focus();
});
return false;
}
// Deletes all the associated elements of the chat we want to remove
function deleteThisChat(hash) {
$('#' + hash + ', #page-switch .' + hash).remove();
}
// Closes the given chat
function quitThisChat(xid, hash, type) {
if(type == 'groupchat') {
// Send our unavailable presence
sendPresence(xid + '/' + getMUCNick(hash), 'unavailable');
// Remove all presence database entries for this groupchat
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
var cXID = explodeThis('_', current, 1);
// If the pointer is on a presence from this groupchat
if((explodeThis('_', current, 0) == 'presence') && (bareXID(cXID) == xid)) {
// Generate the hash for the current XID
var cHash = hex_md5(cXID);
// Disable the message textarea
$('#' + cHash + ' .message-area').attr('disabled', true);
// Remove the presence for this XID
removeDB('presence-stanza', cXID);
removeDB('presence-resources', cXID);
presenceFunnel(cXID, cHash);
}
}
} else {
chatStateSend('gone', xid, hash);
}
// Clear MAM storage for this chat
if(xid in MAM_MAP_STATES) {
delete MAM_MAP_STATES[xid];
}
// Get the chat ID which is before
var previous = $('#' + hash).prev().attr('id');
// Remove the chat
deleteThisChat(hash);
// Reset the switcher
if(!exists('#page-switch .switcher.activechan')) {
switchChan(previous);
}
// Reset the notifications
chanCleanNotify(hash);
return false;
}
// Generates the chat logs
function generateChatLog(xid, hash) {
// Get the main values
var path = '#' + hash + ' .';
var content = $(path + 'content').clone().contents();
var avatar = $(path + 'top .avatar-container:first').html();
var nick = $(path + 'top .bc-name').text();
var date = getXMPPTime('local');
var type = $('#' + hash).attr('data-type');
var direction = $('html').attr('dir') || 'ltr';
// Filter the content smileys
$(content).find('img.emoticon').each(function() {
$(this).replaceWith($(this).attr('alt'));
});
// Remove the useless attributes
$(content).removeAttr('data-type').removeAttr('data-stamp');
// Remove the content avatars
$(content).find('.avatar-container').remove();
// Remove the content click events
$(content).find('a').removeAttr('onclick');
// Extract the content HTML code
content = $(content).parent().html();
// No avatar?
if(!avatar || !avatar.match(/data:/))
avatar = 'none';
// POST the values to the server
$.post('./php/generate-chat.php', { 'content': content, 'xid': xid, 'nick': nick, 'avatar': avatar, 'date': date, 'type': type, 'direction': direction }, function(data) {
// Handled!
$(path + 'tooltip-waitlog').replaceWith('<a class="tooltip-actionlog" href="./php/download-chat.php?id=' + data + '" target="_blank">' + _e("Download file!") + '</a>');
});
return false;
}
// Notifies the user from a new incoming message
var CHAT_FOCUS_HASH = null;
function messageNotify(hash, type) {
// Initialize the vars
var chat_switch = '#page-switch .';
var tested = chat_switch + hash;
var active = $(tested).hasClass('activechan');
// We notify the user if he has not the focus on the chat
if(!active || !isFocused() || (CHAT_FOCUS_HASH != hash)) {
if(!active) {
if(type == 'personal')
$(tested + ', ' + chat_switch + 'more-button').addClass('chan-newmessage');
else if(type == 'unread')
$(tested).addClass('chan-unread');
}
// Count the number of pending messages
var pending = 1;
if(exists('#' + hash + '[data-counter]'))
pending = parseInt($('#' + hash).attr('data-counter')) + 1;
$('#' + hash).attr('data-counter', pending);
}
// Update the page title
updateTitle();
}
// Returns the number of pending events
function pendingEvents() {
// Count the number of notifications
var number = 0;
$('.one-counter[data-counter]').each(function() {
number = number + parseInt($(this).attr('data-counter'));
});
return number;
}
// Updates the page title
function updateTitle() {
// Any pending events?
if(exists('.one-counter[data-counter]'))
pageTitle('new');
else
pageTitle('talk');
}
// Cleans the given chat notifications
function chanCleanNotify(hash) {
// We remove the class that tell the user of a new message
var chat_switch = '#page-switch .';
$(chat_switch + hash).removeClass('chan-newmessage chan-unread');
// We reset the global notifications if no more unread messages
if(!$(chat_switch + 'chans .chan-newmessage').size())
$(chat_switch + 'more-button').removeClass('chan-newmessage');
// We reset the chat counter
$('#' + hash).removeAttr('data-counter');
// Update the page title
updateTitle();
}
// Scrolls to the last chat message
function autoScroll(hash) {
// Avoid a JS error
if(exists('#' + hash)) {
var container = document.getElementById('chat-content-' + hash);
// Scroll down!
container.scrollTop = container.scrollHeight;
}
}
// Shows all the buddies in the buddy-list
function showAllBuddies(from) {
// Put a marker
BLIST_ALL = true;
// We switch the two modes
$('.buddy-conf-more-display-unavailable').hide();
$('.buddy-conf-more-display-available').show();
// Security: reset all the groups toggle event
$('#buddy-list .group-buddies').show();
$('#buddy-list .group span').text('-');
// We show the disconnected buddies
$('.hidden-buddy').show();
// We show all the groups
$('#buddy-list .one-group').show();
if(SEARCH_FILTERED)
funnelFilterBuddySearch();
// Store this in the options
if((from == 'roster') && loadedOptions()) {
setDB('options', 'roster-showall', '1');
storeOptions();
}
}
// Shows only the online buddies in the buddy-list
function showOnlineBuddies(from) {
// Remove the marker
BLIST_ALL = false;
// We switch the two modes
$('.buddy-conf-more-display-available').hide();
$('.buddy-conf-more-display-unavailable').show();
// Security: reset all the groups toggle event
$('#buddy-list .group-buddies').show();
$('#buddy-list .group span').text('-');
// We hide the disconnected buddies
$('.hidden-buddy').hide();
// We check the groups to hide
updateGroups();
if(SEARCH_FILTERED)
funnelFilterBuddySearch();
// Store this in the options
if((from == 'roster') && loadedOptions()) {
setDB('options', 'roster-showall', '0');
storeOptions();
}
}
// Focuses on the right input
function inputFocus() {
// No popup shown
if(!exists('.popup'))
$(document).oneTime(10, function() {
$('.focusable:visible:first').focus();
});
}
// Plugin launcher
function launchInterface() {
// Focus on the first visible input
$(window).focus(inputFocus);
}
// Launch this plugin!
$(document).ready(launchInterface);
/*
Jappix - An open social platform
These are the XMPP links handling JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 12/08/12
*/
// Does an action with the provided XMPP link
function xmppLink(link) {
/* REF: http://xmpp.org/registrar/querytypes.html */
// Remove the "xmpp:" string
link = explodeThis(':', link, 1);
// The XMPP URI has no "?"
if(link.indexOf('?') == -1)
checkChatCreate(link, 'chat');
// Parse the URI
else {
var xid = explodeThis('?', link, 0);
var action = explodeThis('?', link, 1);
switch(action) {
// Groupchat
case 'join':
checkChatCreate(xid, 'groupchat');
break;
// Profile
case 'vcard':
openUserInfos(xid);
break;
// Subscription
case 'subscribe':
addThisContact(xid);
break;
// Unsubscription
case 'unsubscribe':
sendRoster(xid, 'remove');
break;
// Private chat
default:
checkChatCreate(xid, 'chat');
break;
}
}
return false;
}
// Gets the links vars (get parameters in URL)
var LINK_VARS = (function() {
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++) {
var hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = trim(decodeURIComponent(hash[1]));
}
return vars;
})();
/*
Jappix - An open social platform
These are the IQ JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 27/08/11
*/
// Handles an incoming IQ packet
function handleIQ(iq) {
// Gets the IQ content
var iqNode = iq.getNode();
var iqFrom = fullXID(getStanzaFrom(iq));
var iqID = iq.getID();
var iqQueryXMLNS = iq.getQueryXMLNS();
var iqQuery = iq.getQuery();
var iqType = iq.getType();
// Build the response
var iqResponse = new JSJaCIQ();
iqResponse.setID(iqID);
iqResponse.setTo(iqFrom);
iqResponse.setType('result');
// OOB request
if((iqQueryXMLNS == NS_IQOOB) && (iqType == 'set')) {
/* REF: http://xmpp.org/extensions/xep-0066.html */
handleOOB(iqFrom, iqID, 'iq', iqNode);
logThis('Received IQ OOB request: ' + iqFrom);
}
// OOB reply
else if(getDB('send/url', iqID)) {
// Get the values
var oob_url = getDB('send/url', iqID);
var oob_desc = getDB('send/desc', iqID);
var notif_id = hex_md5(oob_url + oob_desc + iqType + iqFrom + iqID);
// Error?
if($(iqNode).find('error').size()) {
// Rejected?
if($(iqNode).find('error not-acceptable').size())
newNotification('send_reject', iqFrom, [iqFrom, oob_url, 'iq', iqID, iqNode], oob_desc, notif_id);
// Failed?
else
newNotification('send_fail', iqFrom, [iqFrom, oob_url, 'iq', iqID, iqNode], oob_desc, notif_id);
// Remove the file
$.get(oob_url + '&action=remove');
}
// Success?
else if(iqType == 'result')
newNotification('send_accept', iqFrom, [iqFrom, oob_url, 'iq', iqID, iqNode], oob_desc, notif_id);
}
// Software version query
else if((iqQueryXMLNS == NS_VERSION) && (iqType == 'get')) {
/* REF: http://xmpp.org/extensions/xep-0092.html */
var iqQuery = iqResponse.setQuery(NS_VERSION);
iqQuery.appendChild(iqResponse.buildNode('name', {'xmlns': NS_VERSION}, 'Jappix'));
iqQuery.appendChild(iqResponse.buildNode('version', {'xmlns': NS_VERSION}, JAPPIX_VERSION));
iqQuery.appendChild(iqResponse.buildNode('os', {'xmlns': NS_VERSION}, BrowserDetect.OS));
con.send(iqResponse);
logThis('Received software version query: ' + iqFrom);
}
// Last activity query
else if((iqQueryXMLNS == NS_LAST) && (iqType == 'get')) {
/* REF: http://xmpp.org/extensions/xep-0012.html */
var iqQuery = iqResponse.setQuery(NS_LAST);
iqQuery.setAttribute('seconds', getLastActivity());
con.send(iqResponse);
logThis('Received last activity query: ' + iqFrom);
}
// Privacy lists push
else if((iqQueryXMLNS == NS_PRIVACY) && (iqType == 'set')) {
// REF : http://xmpp.org/extensions/xep-0016.html
// Roster push
con.send(iqResponse);
// Get the lists
$(iqQuery).find('list').each(function() {
getPrivacy($(this).attr('name'));
});
logThis('Received privacy lists push: ' + iqFrom);
}
// Roster push
else if((iqQueryXMLNS == NS_ROSTER) && (iqType == 'set')) {
// REF : http://xmpp.org/extensions/xep-0092.html
// Roster push
con.send(iqResponse);
// Get the values
$(iqQuery).find('item').each(function() {
parseRoster($(this), 'presence');
});
logThis('Received roster push: ' + iqFrom);
}
// Roster Item Exchange query
else if($(iqNode).find('x[xmlns="' + NS_ROSTERX + '"]').size()) {
// Open a new notification
newNotification('rosterx', iqFrom, [iqNode], '');
logThis('Roster Item Exchange from: ' + iqFrom);
}
// Disco info query
else if((iqQueryXMLNS == NS_DISCO_INFO) && (iqType == 'get')) {
/* REF: http://xmpp.org/extensions/xep-0030.html */
var iqQuery = iqResponse.setQuery(NS_DISCO_INFO);
// We set the name of the client
iqQuery.appendChild(iqResponse.buildNode('identity', {
'category': 'client',
'type': 'web',
'name': 'Jappix',
'xmlns': NS_DISCO_INFO
}));
// We set all the supported features
var fArray = myDiscoInfos();
for(i in fArray)
iqQuery.appendChild(iqResponse.buildNode('feature', {'var': fArray[i], 'xmlns': NS_DISCO_INFO}));
con.send(iqResponse);
logThis('Received disco#infos query: ' + iqFrom);
}
// User time query
else if($(iqNode).find('time').size() && (iqType == 'get')) {
/* REF: http://xmpp.org/extensions/xep-0202.html */
var iqTime = iqResponse.appendNode('time', {'xmlns': NS_URN_TIME});
iqTime.appendChild(iqResponse.buildNode('tzo', {'xmlns': NS_URN_TIME}, getDateTZO()));
iqTime.appendChild(iqResponse.buildNode('utc', {'xmlns': NS_URN_TIME}, getXMPPTime('utc')));
con.send(iqResponse);
logThis('Received local time query: ' + iqFrom);
}
// Ping
else if($(iqNode).find('ping').size() && (iqType == 'get')) {
/* REF: http://xmpp.org/extensions/xep-0199.html */
con.send(iqResponse);
logThis('Received a ping: ' + iqFrom);
}
// Not implemented
else if(!$(iqNode).find('error').size() && ((iqType == 'get') || (iqType == 'set'))) {
// Change IQ type
iqResponse.setType('error');
// Append stanza content
for(var i = 0; i < iqNode.childNodes.length; i++)
iqResponse.getNode().appendChild(iqNode.childNodes.item(i).cloneNode(true));
// Append error content
var iqError = iqResponse.appendNode('error', {'xmlns': NS_CLIENT, 'code': '501', 'type': 'cancel'});
iqError.appendChild(iqResponse.buildNode('feature-not-implemented', {'xmlns': NS_STANZAS}));
iqError.appendChild(iqResponse.buildNode('text', {'xmlns': NS_STANZAS}, _e("The feature requested is not implemented by the recipient or server and therefore cannot be processed.")));
con.send(iqResponse);
logThis('Received an unsupported IQ query from: ' + iqFrom);
}
}
/*
Jappix - An open social platform
These are the messages JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda
Last revision: 04/05/12
*/
// Handles the incoming message packets
function handleMessage(message) {
// Error packet? Stop!
if(handleErrorReply(message))
return;
// MAM-forwarded message?
var c_mam = message.getChild('result', NS_URN_MAM);
if(c_mam) {
var c_mam_delay = $(c_mam).find('delay[xmlns="' + NS_URN_DELAY + '"]');
var c_mam_forward = $(c_mam).find('forwarded[xmlns="' + NS_URN_FORWARD + '"]');
if(c_mam_forward.size()) {
handleMessageMAM(c_mam_forward, c_mam_delay);
}
return;
}
// We get the message items
var from = fullXID(getStanzaFrom(message));
var id = message.getID();
var type = message.getType();
var body = trim(message.getBody());
var node = message.getNode();
var subject = trim(message.getSubject());
// Keep raw message body
var raw_body = body;
// We generate some values
var xid = bareXID(from);
var resource = thisResource(from);
var hash = hex_md5(xid);
var xHTML = $(node).find('html body').size();
var GCUser = false;
// This message comes from a groupchat user
if(isPrivate(xid) && ((type == 'chat') || !type) && resource) {
GCUser = true;
xid = from;
hash = hex_md5(xid);
}
// Get message date
var time, stamp;
var delay = readMessageDelay(node);
// Any delay?
if(delay) {
time = relativeDate(delay);
stamp = extractStamp(Date.jab2date(delay));
} else {
time = getCompleteTime();
stamp = extractStamp(new Date());
}
// Received message
if(hasReceived(message))
return messageReceived(hash, id);
// Chatstate message
if(node && !delay && ((((type == 'chat') || !type) && !exists('#page-switch .' + hash + ' .unavailable')) || (type == 'groupchat'))) {
/* REF: http://xmpp.org/extensions/xep-0085.html */
// Re-process the hash
var chatstate_hash = hash;
if(type == 'groupchat')
chatstate_hash = hex_md5(from);
// Do something depending of the received state
if($(node).find('active').size()) {
displayChatState('active', chatstate_hash, type);
// Tell Jappix the entity supports chatstates
$('#' + chatstate_hash + ' .message-area').attr('data-chatstates', 'true');
logThis('Active chatstate received from: ' + from);
}
else if($(node).find('composing').size()) {
displayChatState('composing', chatstate_hash, type);
logThis('Composing chatstate received from: ' + from);
}
else if($(node).find('paused').size()) {
displayChatState('paused', chatstate_hash, type);
logThis('Paused chatstate received from: ' + from);
}
else if($(node).find('inactive').size()){
displayChatState('inactive', chatstate_hash, type);
logThis('Inactive chatstate received from: ' + from);
}
else if($(node).find('gone').size()){
displayChatState('gone', chatstate_hash, type);
logThis('Gone chatstate received from: ' + from);
}
}
// Jappix App message
if(message.getChild('app', 'jappix:app')) {
// Get notification data
var jappix_app_node = $(node).find('app[xmlns="jappix:app"]');
var jappix_app_name = jappix_app_node.find('name');
var jappix_app_name_id = jappix_app_name.attr('id');
var jappix_app_name_value = jappix_app_name.text();
// Jappix Me notification?
if(jappix_app_name_id == 'me') {
// Get more notification data
var jappix_app_data = jappix_app_node.find('data[xmlns="jappix:app:me"]');
var jappix_app_data_action = jappix_app_data.find('action');
var jappix_app_data_url = jappix_app_data.find('url');
var jappix_app_data_action_type = jappix_app_data_action.attr('type');
var jappix_app_data_action_success = jappix_app_data_action.attr('success');
var jappix_app_data_action_job = jappix_app_data_action.attr('job');
var jappix_app_data_url_value = jappix_app_data_url.text();
// Validate data
if(jappix_app_data_action_type && jappix_app_data_action_success && jappix_app_data_action_job) {
// Filter success
jappix_app_data_action_success = parseInt(jappix_app_data_action_success) == 1 ? 'success' : 'error';
// Generate notification namespace
var jappix_me_notification_ns = jappix_app_name_id + '_' + jappix_app_data_action_type + '_' + jappix_app_data_action_job + '_' + jappix_app_data_action_success;
// Open a new notification
newNotification(jappix_me_notification_ns, xid, [jappix_app_name_value, jappix_app_data_url_value], body);
logThis('Jappix Me notification from: ' + xid + ' with namespace: ' + jappix_me_notification_ns);
return false;
}
}
}
// Invite message
if($(node).find('x[xmlns="' + NS_MUC_USER + '"] invite').size()) {
// We get the needed values
var iFrom = $(node).find('x[xmlns="' + NS_MUC_USER + '"] invite').attr('from');
var iRoom = $(node).find('x[xmlns="' + NS_XCONFERENCE + '"]').attr('jid');
// Old invite method?
if(!iRoom)
iRoom = from;
// We display the notification
newNotification('invite_room', iFrom, [iRoom], body);
logThis('Invite Request from: ' + iFrom + ' to join: ' + iRoom);
return false;
}
// Request message
if(message.getChild('confirm', NS_HTTP_AUTH)) {
// Open a new notification
newNotification('request', xid, [message], body);
logThis('HTTP Request from: ' + xid);
return false;
}
// OOB message
if(message.getChild('x', NS_XOOB)) {
handleOOB(from, id, 'x', node);
logThis('Message OOB request from: ' + xid);
return false;
}
// Roster Item Exchange message
if(message.getChild('x', NS_ROSTERX)) {
// Open a new notification
newNotification('rosterx', xid, [message], body);
logThis('Roster Item Exchange from: ' + xid);
return false;
}
// Normal message
if((type == 'normal') && body) {
// Message date
var messageDate = delay;
// No message date?
if(!messageDate)
messageDate = getXMPPTime('utc');
// Message ID
var messageID = hex_md5(xid + subject + messageDate);
// We store the received message
storeInboxMessage(xid, subject, body, 'unread', messageID, messageDate);
// Display the inbox message
if(exists('#inbox'))
displayInboxMessage(xid, subject, body, 'unread', messageID, messageDate);
// Check we have new messages (play a sound if any unread messages)
if(checkInboxMessages())
soundPlay(2);
// Send it to the server
storeInbox();
return false;
}
// PubSub event
if($(node).find('event').attr('xmlns') == NS_PUBSUB_EVENT) {
// We get the needed values
var iParse = $(node).find('event items');
var iNode = iParse.attr('node');
// Turn around the different result cases
if(iNode) {
switch(iNode) {
// Mood
case NS_MOOD:
// Retrieve the values
var iMood = iParse.find('mood');
var fValue = '';
var tText = '';
// There's something
if(iMood.children().size()) {
// Read the value
fValue = node.getElementsByTagName('mood').item(0).childNodes.item(0).nodeName;
// Read the text
tText = iMood.find('text').text();
// Avoid errors
if(!fValue)
fValue = '';
}
// Store the PEP event (and display it)
storePEP(xid, 'mood', fValue, tText);
break;
// Activity
case NS_ACTIVITY:
// Retrieve the values
var iActivity = iParse.find('activity');
var sValue = '';
var tText = '';
// There's something
if(iActivity.children().size()) {
// Read the value
fValue = node.getElementsByTagName('activity').item(0).childNodes.item(0).nodeName;
// Read the text
tText = iActivity.find('text').text();
// Avoid errors
if(!fValue)
fValue = '';
}
// Store the PEP event (and display it)
storePEP(xid, 'activity', fValue, tText);
break;
// Tune
case NS_TUNE:
// Retrieve the values
var iTune = iParse.find('tune');
var tArtist = iTune.find('artist').text();
var tSource = iTune.find('source').text();
var tTitle = iTune.find('title').text();
var tURI = iTune.find('uri').text();
// Store the PEP event (and display it)
storePEP(xid, 'tune', tArtist, tTitle, tSource, tURI);
break;
// Geolocation
case NS_GEOLOC:
// Retrieve the values
var iGeoloc = iParse.find('geoloc');
var tLat = iGeoloc.find('lat').text();
var tLon = iGeoloc.find('lon').text();
// Any extra-values?
var tLocality = iGeoloc.find('locality').text();
var tRegion = iGeoloc.find('region').text();
var tCountry = iGeoloc.find('country').text();
var tHuman = humanPosition(tLocality, tRegion, tCountry);
// Store the PEP event (and display it)
storePEP(xid, 'geoloc', tLat, tLon, tHuman);
break;
// Microblog
case NS_URN_MBLOG:
displayMicroblog(message, xid, hash, 'mixed', 'push');
break;
// Inbox
case NS_URN_INBOX:
// Do not handle friend's notifications
if(xid == getXID())
handleNotifications(message);
break;
}
}
return false;
}
// If this is a room topic message
if(subject && (type == 'groupchat')) {
// Filter the vars
var filter_subject = subject.replace(/\n+/g, ' ');
var filteredSubject = filterThisMessage(filter_subject, resource, true);
var filteredName = resource.htmlEnc();
// Display the new subject at the top
$('#' + hash + ' .top .name .bc-infos .muc-topic').replaceWith('<span class="muc-topic" title="' + filter_subject + '">' + filteredSubject + '</span>');
// Display the new subject as a system message
if(resource) {
var topic_body = filteredName + ' ' + _e("changed the subject to:") + ' ' + filterThisMessage(subject, resource, true);
displayMessage(type, from, hash, filteredName, topic_body, time, stamp, 'system-message', false);
}
}
// If the message has a content
if(xHTML || body) {
var filteredMessage;
var html_escape = true;
// IE bug fix
if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9))
xHTML = 0;
//If this is a xHTML message
if(xHTML) {
html_escape = false;
// Filter the xHTML message
body = filterThisXHTML(node);
}
// Groupchat message
if(type == 'groupchat') {
/* REF: http://xmpp.org/extensions/xep-0045.html */
// We generate the message type and time
var message_type = 'user-message';
// This is an old message
if(delay && resource)
message_type = 'old-message';
// This is a system message
else if(!resource)
message_type = 'system-message';
var nickQuote = '';
// If this is not an old message
if(message_type == 'user-message') {
var myNick = getMUCNick(hash);
// If an user quoted our nick (with some checks)
var regex = new RegExp('((^)|( )|(@))' + escapeRegex(myNick) + '(($)|(:)|(,)|( ))', 'gi');
if(body.match(regex) && (myNick != resource) && (message_type == 'user-message'))
nickQuote = ' my-nick';
// We notify the user if there's a new personal message
if(nickQuote) {
messageNotify(hash, 'personal');
quickBoard(from, 'groupchat', raw_body, resource);
soundPlay(1);
}
// We notify the user there's a new unread MUC message
else {
messageNotify(hash, 'unread');
// Play sound to all users in the MUC, except user who sent the message.
if(myNick != resource)
soundPlay(1);
}
}
// Display the received message
displayMessage(type, from, hash, resource.htmlEnc(), body, time, stamp, message_type, html_escape, nickQuote);
}
// Chat message
else {
// Gets the nickname of the user
var fromName = resource;
var chatType = 'chat';
// Must send a receipt notification?
if(hasReceipt(message) && (id != null))
sendReceived(type, from, id);
// It does not come from a groupchat user, get the full name
if(!GCUser)
fromName = getBuddyName(xid);
else
chatType = 'private';
// If the chat isn't yet opened, open it !
if(!exists('#' + hash)) {
// We create a new chat
chatCreate(hash, xid, fromName, chatType);
// We tell the user that a new chat has started
soundPlay(0);
} else {
soundPlay(1);
}
// Display the received message
displayMessage(type, xid, hash, fromName.htmlEnc(), body, time, stamp, 'user-message', html_escape, '', 'him');
// We notify the user
messageNotify(hash, 'personal');
quickBoard(xid, 'chat', raw_body, fromName);
}
return false;
}
return false;
}
// Sends a given message
function sendMessage(hash, type) {
// Get the values
var message_area = $('#' + hash + ' .message-area');
var body = trim(message_area.val());
var xid = unescape(message_area.attr('data-to'));
// If the user didn't entered any message, stop
if(!body || !xid)
return false;
try {
// We send the message through the XMPP network
var aMsg = new JSJaCMessage();
aMsg.setTo(xid);
// Set an ID
var id = genID();
aMsg.setID(id);
// /help shortcut
if(body.match(/^\/help\s*(.*)/)) {
// Help text
var help_text = '<p class="help" xmlns="http://www.w3.org/1999/xhtml">';
help_text += '<b>' + _e("Available shortcuts:") + '</b>';
// Shortcuts array
var shortcuts = [];
// Common shortcuts
shortcuts.push(printf(_e("%s removes the chat logs"), '<em>/clear</em>'));
shortcuts.push(printf(_e("%s joins a groupchat"), '<em>/join jid</em>'));
shortcuts.push(printf(_e("%s closes the chat"), '<em>/part</em>'));
shortcuts.push(printf(_e("%s shows the user profile"), '<em>/whois jid</em>'));
// Groupchat shortcuts
if(type == 'groupchat') {
shortcuts.push(printf(_e("%s sends a message to the room"), '<em>/say message</em>'));
shortcuts.push(printf(_e("%s changes your nickname"), '<em>/nick nickname</em>'));
shortcuts.push(printf(_e("%s sends a message to someone in the room"), '<em>/msg nickname message</em>'));
shortcuts.push(printf(_e("%s changes the room topic"), '<em>/topic subject</em>'));
shortcuts.push(printf(_e("%s kicks a user of the room"), '<em>/kick [reason:] nickname</em>'));
shortcuts.push(printf(_e("%s bans a user of the room"), '<em>/ban [reason:] nickname</em>'));
shortcuts.push(printf(_e("%s invites someone to join the room"), '<em>/invite jid message</em>'));
}
// Generate the code from the array
shortcuts = shortcuts.sort();
for(s in shortcuts)
help_text += shortcuts[s] + '<br />';
help_text += '</p>';
// Display the message
displayMessage(type, xid, hash, 'help', help_text, getCompleteTime(), getTimeStamp(), 'system-message', false);
// Reset chatstate
chatStateSend('active', xid, hash);
}
// /clear shortcut
else if(body.match(/^\/clear/)) {
cleanChat(hex_md5(xid));
// Reset chatstate
chatStateSend('active', xid, hash);
}
// /join shortcut
else if(body.match(/^\/join (\S+)\s*(.*)/)) {
// Join
var room = generateXID(RegExp.$1, 'groupchat');
var pass = RegExp.$2;
checkChatCreate(room, 'groupchat');
// Reset chatstate
chatStateSend('active', xid, hash);
}
// /part shortcut
else if(body.match(/^\/part\s*(.*)/) && (!isAnonymous() || (isAnonymous() && (xid != generateXID(ANONYMOUS_ROOM, 'groupchat')))))
quitThisChat(xid, hex_md5(xid), type);
// /whois shortcut
else if(body.match(/^\/whois(( (\S+))|($))/)) {
var whois_xid = RegExp.$3;
// Groupchat WHOIS
if(type == 'groupchat') {
var nXID = getMUCUserXID(xid, whois_xid);
if(!nXID)
openThisInfo(6);
else
openUserInfos(nXID);
}
// Chat or private WHOIS
else {
if(!whois_xid)
openUserInfos(xid);
else
openUserInfos(whois_xid);
}
// Reset chatstate
chatStateSend('active', xid, hash);
}
// Chat message type
else if(type == 'chat') {
aMsg.setType('chat');
// Generates the correct message depending of the choosen style
var genMsg = generateMessage(aMsg, body, hash);
var html_escape = genMsg != 'XHTML';
// Receipt request
var receipt_request = receiptRequest(hash);
if(receipt_request)
aMsg.appendNode('request', {'xmlns': NS_URN_RECEIPTS});
// Chatstate
aMsg.appendNode('active', {'xmlns': NS_CHATSTATES});
// Send it!
con.send(aMsg, handleErrorReply);
// Filter the xHTML message (for us!)
if(!html_escape)
body = filterThisXHTML(aMsg.getNode());
// Finally we display the message we just sent
var my_xid = getXID();
displayMessage('chat', my_xid, hash, getBuddyName(my_xid).htmlEnc(), body, getCompleteTime(), getTimeStamp(), 'user-message', html_escape, '', 'me', id);
// Receipt timer
if(receipt_request)
checkReceived(hash, id);
}
// Groupchat message type
else if(type == 'groupchat') {
// /say shortcut
if(body.match(/^\/say (.+)/)) {
body = body.replace(/^\/say (.+)/, '$1');
aMsg.setType('groupchat');
generateMessage(aMsg, body, hash);
con.send(aMsg, handleErrorReply);
}
// /nick shortcut
else if(body.match(/^\/nick (.+)/)) {
var nick = body.replace(/^\/nick (.+)/, '$1');
// Does not exist yet?
if(!getMUCUserXID(xid, nick)) {
// Send a new presence
sendPresence(xid + '/' + nick, '', getUserShow(), getUserStatus(), '', false, false, handleErrorReply);
// Change the stored nickname
$('#' + hex_md5(xid)).attr('data-nick', escape(nick));
// Reset chatstate
chatStateSend('active', xid, hash);
}
}
// /msg shortcut
else if(body.match(/^\/msg (\S+)\s+(.+)/)) {
var nick = RegExp.$1;
var body = RegExp.$2;
var nXID = getMUCUserXID(xid, nick);
// We check if the user exists
if(!nXID)
openThisInfo(6);
// If the private message is not empty
else if(body) {
aMsg.setType('chat');
aMsg.setTo(nXID);
generateMessage(aMsg, body, hash);
con.send(aMsg, handleErrorReply);
}
}
// /topic shortcut
else if(body.match(/^\/topic (.+)/)) {
var topic = body.replace(/^\/topic (.+)/, '$1');
aMsg.setType('groupchat');
aMsg.setSubject(topic);
con.send(aMsg, handleMessageError);
// Reset chatstate
chatStateSend('active', xid, hash);
}
// /ban shortcut
else if(body.match(/^\/ban (.*)/)) {
nick = jQuery.trim(RegExp.$1);
reason='';
var nXID = getMUCUserRealXID(xid, nick);
// We check if the user exists, if not it may be because a reason is given
// we do not check it at first because the nickname could contain ':'
if(!nXID && (body.match(/^\/ban ([^:]+)[:]*(.*)/))) {
var reason = jQuery.trim(RegExp.$1);
var nick = jQuery.trim(RegExp.$2);
if (0==nick.length) {
nick = reason;
reason='';
}
var nXID = getMUCUserXID(xid, nick);
}
// We check if the user exists
if(!nXID)
openThisInfo(6);
else {
// We generate the ban IQ
var iq = new JSJaCIQ();
iq.setTo(xid);
iq.setType('set');
var iqQuery = iq.setQuery(NS_MUC_ADMIN);
var item = iqQuery.appendChild(iq.buildNode('item', {'affiliation': 'outcast', 'jid': nXID, 'xmlns': NS_MUC_ADMIN}));
if(reason)
item.appendChild(iq.buildNode('reason', {'xmlns': NS_MUC_ADMIN}, reason));
con.send(iq, handleErrorReply);
}
// Reset chatstate
chatStateSend('active', xid, hash);
}
// /kick shortcut
else if(body.match(/^\/kick (.*)/)) {
nick = jQuery.trim(RegExp.$1);
reason='';
var nXID = getMUCUserRealXID(xid, nick);
// We check if the user exists, if not it may be because a reason is given
// we do not check it at first because the nickname could contain ':'
if(!nXID && (body.match(/^\/kick ([^:]+)[:]*(.*)/))) {
var reason = jQuery.trim(RegExp.$1);
var nick = jQuery.trim(RegExp.$2);
if (0==nick.length) {
nick = reason;
reason='';
}
var nXID = getMUCUserXID(xid, nick);
}
// We check if the user exists
if(!nXID)
openThisInfo(6);
else {
// We generate the kick IQ
var iq = new JSJaCIQ();
iq.setTo(xid);
iq.setType('set');
var iqQuery = iq.setQuery(NS_MUC_ADMIN);
var item = iqQuery.appendChild(iq.buildNode('item', {'nick': nick, 'role': 'none', 'xmlns': NS_MUC_ADMIN}));
if(reason)
item.appendChild(iq.buildNode('reason', {'xmlns': NS_MUC_ADMIN}, reason));
con.send(iq, handleErrorReply);
}
// Reset chatstate
chatStateSend('active', xid, hash);
}
// /invite shortcut
else if(body.match(/^\/invite (\S+)\s*(.*)/)) {
var i_xid = RegExp.$1;
var reason = RegExp.$2;
var x = aMsg.appendNode('x', {'xmlns': NS_MUC_USER});
var aNode = x.appendChild(aMsg.buildNode('invite', {'to': i_xid, 'xmlns': NS_MUC_USER}));
if(reason)
aNode.appendChild(aMsg.buildNode('reason', {'xmlns': NS_MUC_USER}, reason));
con.send(aMsg, handleErrorReply);
// Reset chatstate
chatStateSend('active', xid, hash);
}
// No shortcut, this is a message
else {
aMsg.setType('groupchat');
// Chatstate
aMsg.appendNode('active', {'xmlns': NS_CHATSTATES});
generateMessage(aMsg, body, hash);
con.send(aMsg, handleMessageError);
logThis('Message sent to: ' + xid + ' / ' + type, 3);
}
}
// We reset the message input
$('#' + hash + ' .message-area').val('');
}
finally {
return false;
}
}
// Generates the correct message area style
function generateStyle(hash) {
// Initialize the vars
var styles = '#' + hash + ' div.bubble-style';
var font = styles + ' a.font-current';
var fontsize = styles + ' a.fontsize-current';
var checkbox = styles + ' input[type="checkbox"]';
var color = '#' + hash + ' .message-area[data-color]';
var style = '';
// Get the font value
$(font).filter('[data-font]').each(function() {
style += 'font-family: ' + $(this).attr('data-font') + ';';
});
// Get the font value
$(fontsize).filter('[data-value]').each(function() {
style += 'font-size: ' + $(this).attr('data-value') + 'px;';
});
// Loop the input values
$(checkbox).filter(':checked').each(function() {
// If there is a previous element
if(style)
style += ' ';
// Get the current style
switch($(this).attr('class')) {
case 'bold':
style += 'font-weight: bold;';
break;
case 'italic':
style += 'font-style: italic;';
break;
case 'underline':
style += 'text-decoration: underline;';
break;
}
});
// Get the color value
$(color).each(function() {
style += 'color: #' + $(this).attr('data-color');
});
return style;
}
// Generates the correct message code
function generateMessage(aMsg, body, hash) {
// Create the classical body
aMsg.setBody(body);
// Get the style
var style = $('#' + hash + ' .message-area').attr('style');
// A message style is choosen
if(style) {
// Explode the message body new lines (to create one <p /> element by line)
var new_lines = new Array(body);
if(body.match(/\n/))
new_lines = body.split('\n');
// Create the XML elements
var aHtml = aMsg.appendNode('html', {'xmlns': NS_XHTML_IM});
var aBody = aHtml.appendChild(aMsg.buildNode('body', {'xmlns': NS_XHTML}));
// Use the exploded body array to create one element per entry
for(i in new_lines) {
// Current line
var cLine = new_lines[i];
// Blank line, we put a <br />
if(cLine.match(/(^)(\s+)($)/) || !cLine)
aBody.appendChild(aMsg.buildNode('br', {'xmlns': NS_XHTML}));
// Line with content, we put a <p />
else {
// HTML encode the line
cLine = cLine.htmlEnc();
// Filter the links
cLine = applyLinks(cLine, 'xhtml-im', style);
// Append the filtered line
$(aBody).append($('<p style="' + style + '">' + cLine + '</p>'));
}
}
return 'XHTML';
}
return 'PLAIN';
}
// Displays a given message in a chat tab
function displayMessage(type, xid, hash, name, body, time, stamp, message_type, html_escape, nick_quote, mode, id, c_target_sel, no_scroll) {
// Target
if(typeof c_target_sel === 'undefined') {
c_target_sel = $('#' + hash + ' .content');
}
// Generate some stuffs
var has_avatar = false;
var xid_hash = '';
if(!nick_quote) {
nick_quote = '';
}
if(message_type != 'system-message') {
has_avatar = true;
xid_hash = hex_md5(xid);
}
// Can scroll?
var cont_scroll = document.getElementById('chat-content-' + hash);
var can_scroll = false;
if((!cont_scroll.scrollTop || ((cont_scroll.clientHeight + cont_scroll.scrollTop) == cont_scroll.scrollHeight)) && no_scroll != true) {
can_scroll = true;
}
// Any ID?
var data_id = '';
if(id) {
data_id = ' data-id="' + id + '"';
}
// Filter the message
var filteredMessage = filterThisMessage(body, name, html_escape);
// Display the received message in the room
var messageCode = '<div class="one-line ' + message_type + nick_quote + '" data-stamp="' + stamp + '"' + data_id + '>';
// Name color attribute
if(type == 'groupchat') {
attribute = ' style="color: ' + generateColor(name) + ';" class="name';
} else {
attribute = ' class="name';
if(mode) {
attribute += ' ' + mode;
}
}
// Close the class attribute
if(message_type == 'system-message') {
attribute += ' hidden"';
} else {
attribute += '"';
}
// Filter the previous displayed message
var last = c_target_sel.find('.one-group:not(.from-history):last');
var last_name = last.find('b.name').attr('data-xid');
var last_type = last.attr('data-type');
var last_stamp = parseInt(last.attr('data-stamp'));
var grouped = false;
// We can group it with another previous message
if((last_name == xid) && (message_type == last_type) && ((stamp - last_stamp) <= 1800)) {
grouped = true;
}
// Is it a /me command?
if(body.match(/(^|>)(\/me )([^<]+)/)) {
filteredMessage = '<i>' + filteredMessage + '</i>';
}
messageCode += filteredMessage + '</div>';
// Must group it?
if(!grouped) {
// Generate message headers
var message_head = '';
// Any avatar to add?
if(has_avatar) {
message_head += '<div class="avatar-container"><img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" /></div>';
}
// Add the date & the name
message_head += '<span class="date">' + time + '</span><b data-xid="' + encodeQuotes(xid) + '" ' + attribute + '>' + name + '</b>';
// Generate message code
messageCode = '<div class="one-group ' + xid_hash + '" data-type="' + message_type + '" data-stamp="' + stamp + '">' + message_head + messageCode + '</div>';
}
// Write the code in the DOM
if(grouped) {
c_target_sel.find('.one-group:last').append(messageCode);
} else {
c_target_sel.append(messageCode);
}
// Store the last MAM_REQ_MAX message groups
if(!enabledMAM() && (type == 'chat') && (message_type == 'user-message')) {
// Filter the DOM
var dom_filter = $('#' + hash + ' .content').clone().contents();
var default_avatar = ('./img/others/default-avatar.png').replace(/&amp;/g, '&'); // Fixes #252
$(dom_filter).find('.system-message').parent().remove();
$(dom_filter).find('.avatar-container img.avatar').attr('src', default_avatar);
$(dom_filter).find('.one-line').parent().slice(0, $(dom_filter).find('.one-line').parent().size() - MAM_REQ_MAX).remove();
var store_html = $(dom_filter).parent().html();
// Store the data
if(store_html) {
setPersistent(getXID(), 'history', hash, store_html);
}
}
// Must get the avatar?
if(has_avatar && xid && !grouped) {
getAvatar(xid, 'cache', 'true', 'forget');
}
// Scroll to this message
if(can_scroll) {
autoScroll(hash);
}
}
/*
Jappix - An open social platform
These are the chatstate JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 25/08/11
*/
// Sends a given chatstate to a given entity
function chatStateSend(state, xid, hash) {
var user_type = $('#' + hash).attr('data-type');
// If the friend client supports chatstates and is online
if((user_type == 'groupchat') || ((user_type == 'chat') && $('#' + hash + ' .message-area').attr('data-chatstates') && !exists('#page-switch .' + hash + ' .unavailable'))) {
// Already sent?
if(getDB('currentchatstate', xid) == state)
return;
// Write the state
setDB('currentchatstate', xid, state);
// New message stanza
var aMsg = new JSJaCMessage();
aMsg.setTo(xid);
aMsg.setType(user_type);
// Append the chatstate node
aMsg.appendNode(state, {'xmlns': NS_CHATSTATES});
// Send this!
con.send(aMsg);
}
}
// Displays a given chatstate in a given chat
function displayChatState(state, hash, type) {
// Groupchat?
if(type == 'groupchat') {
resetChatState(hash, type);
// "gone" state not allowed
if(state != 'gone')
$('#page-engine .page-engine-chan .user.' + hash).addClass(state);
}
// Chat
else {
// We change the buddy name color in the page-switch
resetChatState(hash, type);
$('#page-switch .' + hash + ' .name').addClass(state);
// We generate the chatstate text
var text = '';
switch(state) {
// Active
case 'active':
text = _e("Your friend is paying attention to the conversation.");
break;
// Composing
case 'composing':
text = _e("Your friend is writing a message...");
break;
// Paused
case 'paused':
text = _e("Your friend stopped writing a message.");
break;
// Inactive
case 'inactive':
text = _e("Your friend is doing something else.");
break;
// Gone
case 'gone':
text = _e("Your friend closed the chat.");
break;
}
// We reset the previous state
$('#' + hash + ' .chatstate').remove();
// We create the chatstate
$('#' + hash + ' .content').after('<div class="' + state + ' chatstate">' + text + '</div>');
}
}
// Resets the chatstate switcher marker
function resetChatState(hash, type) {
// Define the selector
var selector;
if(type == 'groupchat')
selector = $('#page-engine .page-engine-chan .user.' + hash);
else
selector = $('#page-switch .' + hash + ' .name');
// Reset!
selector.removeClass('active')
selector.removeClass('composing')
selector.removeClass('paused')
selector.removeClass('inactive')
selector.removeClass('gone');
}
// Adds the chatstate events
function eventsChatState(target, xid, hash, type) {
target.keyup(function(e) {
if(e.keyCode != 13) {
// Composing a message
if($(this).val() && (getDB('chatstate', xid) != 'on')) {
// We change the state detect input
setDB('chatstate', xid, 'on');
// We send the friend a "composing" chatstate
chatStateSend('composing', xid, hash);
}
// Flushed the message which was being composed
else if(!$(this).val() && (getDB('chatstate', xid) == 'on')) {
// We change the state detect input
setDB('chatstate', xid, 'off');
// We send the friend an "active" chatstate
chatStateSend('active', xid, hash);
}
}
});
target.change(function() {
// Reset the composing database entry
setDB('chatstate', xid, 'off');
});
target.focus(function() {
// Not needed
if(target.is(':disabled'))
return;
// Something was written, user started writing again
if($(this).val())
chatStateSend('composing', xid, hash);
// Chat only: Nothing in the input, user is active
else if(type == 'chat')
chatStateSend('active', xid, hash);
});
target.blur(function() {
// Not needed
if(target.is(':disabled'))
return;
// Something was written, user paused
if($(this).val())
chatStateSend('paused', xid, hash);
// Chat only: Nothing in the input, user is inactive
else if(type == 'chat')
chatStateSend('inactive', xid, hash);
});
}
/*
Jappix - An open social platform
These are the receipts JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 26/12/10
*/
// Checks if we can send a receipt request
function receiptRequest(hash) {
// Entity have support for receipt?
if($('#' + hash + ' .message-area').attr('data-receipts') == 'true')
return true;
return false;
}
// Checks if there is a receipt request
function hasReceipt(packet) {
// Any receipt request?
if(packet.getChild('request', NS_URN_RECEIPTS))
return true;
return false;
}
// Checks if there is a received reply
function hasReceived(packet) {
// Any received reply?
if(packet.getChild('received', NS_URN_RECEIPTS))
return true;
return false;
}
// Sends a received notification
function sendReceived(type, to, id) {
var aMsg = new JSJaCMessage();
aMsg.setTo(to);
aMsg.setID(id);
// Any type?
if(type)
aMsg.setType(type);
// Append the received node
aMsg.appendNode('received', {'xmlns': NS_URN_RECEIPTS, 'id': id});
con.send(aMsg);
logThis('Sent received to: ' + to);
}
// Tells the message has been received
function messageReceived(hash, id) {
// Line selector
var path = $('#' + hash + ' .one-line[data-id="' + id + '"]');
// Add a received marker
path.attr('data-received', 'true')
.removeAttr('data-lost');
// Group selector
var group = path.parent();
// Remove the group marker
if(!group.find('.one-line[data-lost]').size()) {
group.find('b.name').removeClass('talk-images')
.removeAttr('title');
}
return false;
}
// Checks if the message has been received
function checkReceived(hash, id) {
// Fire a check 10 seconds later
$('#' + hash + ' .one-line[data-id="' + id + '"]').oneTime('10s', function() {
// Not received?
if($(this).attr('data-received') != 'true') {
// Add a "lost" marker
$(this).attr('data-lost', 'true');
// Add a warn on the buddy-name
$(this).parent().find('b.name').addClass('talk-images')
.attr('title', _e("Your friend seems not to have received your message(s)!"));
}
});
}
/*
Jappix - An open social platform
These are the tooltip JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 10/04/12
*/
// Creates a tooltip code
function createTooltip(xid, hash, type) {
// Path to the element
var path = '#' + hash;
var path_tooltip = path + ' .chat-tools-' + type;
var path_bubble = path_tooltip + ' .bubble-' + type;
// Yet exists?
if(exists(path_bubble))
return false;
// Generates special tooltip HTML code
var title = '';
var content = '';
switch(type) {
// Smileys
case 'smileys':
title = _e("Smiley insertion");
content = smileyLinks(hash);
break;
// Style
case 'style':
title = _e("Change style");
// Generate fonts list
var fonts = {
'arial': 'Arial, Helvetica, sans-serif',
'arial-black': '\'Arial Black\', Gadget, sans-serif',
'bookman-old-style': '\'Bookman Old Style\', serif',
'comic-sans-ms': '\'Comic Sans MS\', cursive',
'courier': 'Courier, monospace',
'courier-new': '\'Courier New\', Courier, monospace',
'garamond': 'Garamond, serif',
'georgia': 'Georgia, serif',
'impact': 'Impact, Charcoal, sans-serif',
'lucida-console': '\'Lucida Console\', Monaco, monospace',
'lucida-sans-unicode': '\'Lucida Sans Unicode\', \'Lucida Grande\', sans-serif',
'ms-sans-serif': '\'MS Sans Serif\', Geneva, sans-serif',
'ms-serif': '\'MS Serif\', \'New York\', sans-serif',
'palatino-linotype': '\'Palatino Linotype\', \'Book Antiqua\', Palatino, serif',
'symbol': 'Symbol, sans-serif',
'tahoma': 'Tahoma, Geneva, sans-serif',
'times-new-roman': '\'Times New Roman\', Times, serif',
'trebuchet-ms': '\'Trebuchet MS\', Helvetica, sans-serif',
'verdana': 'Verdana, Geneva, sans-serif',
'webdings': 'Webdings, sans-serif',
'wingdings': 'Wingdings, \'Zapf Dingbats\', sans-serif'
};
var fonts_html = '<div class="font-list">';
// No fonts
fonts_html += '<a href="#">' + _e("None") + '</a>';
// Available fonts
$.each(fonts, function(id_name, full_name) {
// Generate short name
var short_name = full_name;
if(short_name.match(/,/)) {
var name_split = short_name.split(',');
short_name = trim(name_split[0]);
}
short_name = short_name.replace(/([^a-z0-9\s]+)/gi, '');
// Add this to the HTML
fonts_html += '<a href="#" data-value="' + encodeQuotes(id_name) + '" data-font="' + encodeQuotes(full_name) + '" style="font-family: ' + encodeQuotes(full_name) + ';">' + short_name.htmlEnc() + '</a>';
});
fonts_html += '</div>';
content =
'<label class="font">' +
'<div class="font-icon talk-images"></div>' +
'<div class="fontsize-change">' +
'<a class="fontsize-current" href="#">12</a>' +
'<div class="fontsize-list">' +
'<a href="#" class="reset talk-images"></a>' +
'<a href="#" data-value="10" style="font-size: 10px;">10</a>' +
'<a href="#" data-value="12" style="font-size: 12px;">12</a>' +
'<a href="#" data-value="14" style="font-size: 14px;">14</a>' +
'<a href="#" data-value="16" style="font-size: 16px;">16</a>' +
'<a href="#" data-value="18" style="font-size: 18px;">18</a>' +
'</div>' +
'</div>' +
'<div class="font-change">' +
'<a class="font-current" href="#">' + _e("None") + '</a>' +
fonts_html +
'</div>' +
'</label>' +
'<label class="bold"><input type="checkbox" class="bold" />' + _e("Text in bold") + '</label>' +
'<label class="italic"><input type="checkbox" class="italic" />' + _e("Text in italic") + '</label>' +
'<label class="underline"><input type="checkbox" class="underline" />' + _e("Underlined text") + '</label>' +
'<a href="#" class="color" style="background-color: #b10808; clear: both;" data-color="b10808"></a>' +
'<a href="#" class="color" style="background-color: #e5860c;" data-color="e5860c"></a>' +
'<a href="#" class="color" style="background-color: #f0f30e;" data-color="f0f30e"></a>' +
'<a href="#" class="color" style="background-color: #009a04;" data-color="009a04"></a>' +
'<a href="#" class="color" style="background-color: #0ba9a0;" data-color="0ba9a0"></a>' +
'<a href="#" class="color" style="background-color: #04228f;" data-color="04228f"></a>' +
'<a href="#" class="color" style="background-color: #9d0ab7;" data-color="9d0ab7"></a>' +
'<div class="color-picker">' +
'<a href="#" class="color-more talk-images"></a>' +
'<div class="color-hex">' +
'<span class="hex-begin">#</span>' +
'<input class="hex-value" type="text" maxlength="6" placeholder="e1e1e1" />' +
'</div>' +
'</div>';
break;
// File send
case 'file':
title = _e("Send a file");
content = '<p style="margin-bottom: 8px;">' + _e("Once uploaded, your friend will be prompted to download the file you sent.") + '</p>';
content += '<form id="oob-upload" action="./php/send.php" method="post" enctype="multipart/form-data">' + generateFileShare() + '</form>';
break;
// Chat log
case 'save':
title = _e("Save chat");
content = '<p style="margin-bottom: 8px;">' + _e("Click on the following link to get the chat log, and wait. Then click again to get the file.") + '</p>';
// Possible to generate any log?
if($(path + ' .one-line').size())
content += '<a href="#" class="tooltip-actionlog">' + _e("Generate file!") + '</a>';
else
content += '<span class="tooltip-nolog">' + _e("This chat is empty!") + '</span>';
break;
}
// Generates general tooltip HTML code
var html =
'<div class="tooltip bubble-' + type + '">' +
'<div class="tooltip-subitem">' +
'<p class="tooltip-top">' + title + '</p>' +
content +
'</div>' +
'<div class="tooltip-subarrow talk-images"></div>' +
'</div>';
// Append the HTML code
$(path_tooltip).append(html);
// Special events
switch(type) {
// Smileys
case 'smileys':
// Apply click event on smiley links
$(path_tooltip + ' a.emoticon').click(function() {
return insertSmiley($(this).attr('data-smiley'), hash);
});
break;
// Style
case 'style':
// Paths to items
var message_area = path + ' .message-area';
var bubble_style = path_tooltip + ' .bubble-style';
var style = bubble_style + ' input:checkbox';
var colors = bubble_style + ' a.color';
var font_current = bubble_style + ' a.font-current';
var font_list = bubble_style + ' div.font-list';
var font_select = font_list + ' a';
var fontsize_current = bubble_style + ' a.fontsize-current';
var fontsize_list = bubble_style + ' div.fontsize-list';
var fontsize_select = fontsize_list + ' a';
var color = bubble_style + ' div.color-picker';
var color_more = color + ' a.color-more';
var color_hex = color + ' div.color-hex';
// Click event on style bubble
$(bubble_style).click(function() {
// Hide font selector if opened
if($(font_list).is(':visible'))
$(font_current).click();
// Hide font-size selector if opened
if($(fontsize_list).is(':visible'))
$(fontsize_current).click();
// Hide color selector if opened
if($(color_hex).is(':visible'))
$(color_more).click();
});
// Click event on font picker
$(font_current).click(function() {
// The clicked color is yet selected
if($(font_list).is(':visible'))
$(this).parent().removeClass('listed');
else
$(this).parent().addClass('listed');
return false;
});
// Click event on a new font in the picker
$(font_select).click(function() {
// No font selected
if(!$(this).attr('data-value')) {
$(font_current).removeAttr('data-font')
.removeAttr('data-value')
.text(_e("None"));
$(message_area).removeAttr('data-font');
}
// A font is defined
else {
$(font_current).attr('data-font', $(this).attr('data-font'))
.attr('data-value', $(this).attr('data-value'))
.text($(font_list).find('a[data-value="' + $(this).attr('data-value') + '"]').text());
$(message_area).attr('data-font', $(this).attr('data-value'));
}
return false;
});
// Click event on font-size picker
$(fontsize_current).click(function() {
// The clicked color is yet selected
if($(fontsize_list).is(':visible'))
$(this).parent().removeClass('listed');
else
$(this).parent().addClass('listed');
return false;
});
// Click event on a new font-size in the picker
$(fontsize_select).click(function() {
// No font-size selected
if(!$(this).attr('data-value')) {
$(fontsize_current).removeAttr('data-value').text(_e("16"));
$(message_area).removeAttr('data-fontsize');
}
// A font-size is defined
else {
$(fontsize_current).attr('data-value', $(this).attr('data-value'))
.text($(this).attr('data-value'));
$(message_area).attr('data-fontsize', $(this).attr('data-value'));
}
return false;
});
// Click event on color picker
$(colors).click(function() {
// Reset the manual picker
$(color_hex).find('input').val('');
// The clicked color is yet selected
if($(this).hasClass('selected')) {
$(message_area).removeAttr('data-color');
$(this).removeClass('selected');
}
else {
$(message_area).attr('data-color', $(this).attr('data-color'));
$(colors).removeClass('selected');
$(this).addClass('selected');
}
return false;
});
// Click event on color picker
$(color_more).click(function() {
// The clicked color is yet selected
if($(color_hex).is(':visible'))
$(this).parent().removeClass('opened');
else {
$(this).parent().addClass('opened');
// Focus
$(document).oneTime(10, function() {
$(color_hex).find('input').focus();
});
}
return false;
});
// Click event on color hex
$(color_hex).click(function() {
return false;
});
// Keyup event on color picker
$(color_hex).find('input').keyup(function(e) {
// Submit
if(e.keyCode == 13) {
if($(color_hex).is(':visible')) {
$(color_more).click();
// Focus again on the message textarea
$(document).oneTime(10, function() {
$(message_area).focus();
});
}
return false;
}
// Reset current color
$(message_area).removeAttr('data-color');
$(colors).removeClass('selected');
// Change value
var new_value = $(this).val().replace(/([^a-z0-9]+)/gi, '');
$(this).val(new_value);
if(new_value)
$(message_area).attr('data-color', new_value);
// Regenerate style
var style = generateStyle(hash);
// Any style to apply?
if(style)
$(message_area).attr('style', style);
else
$(message_area).removeAttr('style');
}).placeholder();
// Change event on text style checkboxes
$(style).change(function() {
// Get current type
var style_data = 'data-' + $(this).attr('class');
// Checked checkbox?
if($(this).filter(':checked').size())
$(message_area).attr(style_data, true);
else
$(message_area).removeAttr(style_data);
});
// Update the textarea style when it is changed
$(style + ', ' + colors + ', ' + font_select + ', ' + fontsize_select).click(function() {
var style = generateStyle(hash);
// Any style to apply?
if(style)
$(message_area).attr('style', style);
else
$(message_area).removeAttr('style');
// Focus again on the message textarea
$(document).oneTime(10, function() {
$(message_area).focus();
});
});
// Load current style
loadStyleSelector(hash);
break;
// File send
case 'file':
// File upload vars
var oob_upload_options = {
dataType: 'xml',
beforeSubmit: waitUploadOOB,
success: handleUploadOOB
};
// Upload form submit event
$(path_tooltip + ' #oob-upload').submit(function() {
if($(path_tooltip + ' #oob-upload input[type="file"]').val())
$(this).ajaxSubmit(oob_upload_options);
return false;
});
// Upload input change event
$(path_tooltip + ' #oob-upload input[type="file"]').change(function() {
if($(this).val())
$(path_tooltip + ' #oob-upload').ajaxSubmit(oob_upload_options);
return false;
});
// Input click event
$(path_tooltip + ' #oob-upload input[type="file"], ' + path_tooltip + ' #oob-upload input[type="submit"]').click(function() {
if(exists(path_tooltip + ' #oob-upload input[type="reset"]'))
return;
// Lock the bubble
$(path_bubble).addClass('locked');
// Add a cancel button
$(this).after('<input type="reset" value="' + _e("Cancel") + '" />');
// Cancel button click event
$(path_tooltip + ' #oob-upload input[type="reset"]').click(function() {
// Remove the bubble
$(path_bubble).removeClass('locked');
destroyTooltip(hash, 'file');
});
});
break;
// Chat log
case 'save':
// Chat log generation click event
$(path_tooltip + ' .tooltip-actionlog').click(function() {
// Replace it with a waiting notice
$(this).replaceWith('<span class="tooltip-waitlog">' + _e("Please wait...") + '</span>');
generateChatLog(xid, hash);
return false;
});
break;
}
return true;
}
// Destroys a tooltip code
function destroyTooltip(hash, type) {
$('#' + hash + ' .chat-tools-content:not(.mini) .bubble-' + type + ':not(.locked)').remove();
}
// Applies the page-engine tooltips hover event
function hoverTooltip(xid, hash, type) {
$('#' + hash + ' .chat-tools-' + type).hover(function() {
createTooltip(xid, hash, type);
}, function() {
destroyTooltip(hash, type)
});
}
// Applies the hoverTooltip function to the needed things
function tooltipIcons(xid, hash) {
// Hover events
hoverTooltip(xid, hash, 'smileys');
hoverTooltip(xid, hash, 'style');
hoverTooltip(xid, hash, 'file');
hoverTooltip(xid, hash, 'save');
// Click events
$('#' + hash + ' a.chat-tools-content, #' + hash + ' .chat-tools-content a').click(function() {
return false;
});
}
// Loads the style selector options
function loadStyleSelector(hash) {
// Define the vars
var path = '#' + hash;
var message_area = $(path + ' .message-area');
var bubble_style = path + ' .bubble-style';
var font = message_area.attr('data-font');
var font_select = $(bubble_style + ' div.font-list').find('a[data-value="' + font + '"]');
var fontsize = message_area.attr('data-fontsize');
var color = message_area.attr('data-color');
// Apply message font
if(font) {
$(bubble_style + ' a.font-current').attr('data-value', font)
.attr('data-font', font_select.attr('data-font'))
.text(font_select.text());
}
// Apply message font-size
if(fontsize) {
$(bubble_style + ' a.fontsize-current').attr('data-value', fontsize)
.text(fontsize);
}
// Apply the options to the style selector
$(bubble_style + ' input[type="checkbox"]').each(function() {
// Current input enabled?
if(message_area.attr('data-' + $(this).attr('class')))
$(this).attr('checked', true);
});
// Apply message color
if(color) {
if($(bubble_style + ' a.color[data-color="' + color + '"]').size())
$(bubble_style + ' a.color[data-color="' + color + '"]').addClass('selected');
else
$(bubble_style + ' div.color-hex input.hex-value').val(color);
}
}
/*
Jappix - An open social platform
These are the filtering JS script for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda
Last revision: 13/02/12
*/
// Generates a given emoticon HTML code
function emoteImage(image, text, after) {
return ' <img class="emoticon emoticon-' + image + ' smileys-images" alt="' + encodeQuotes(text) + '" src="' + './img/others/blank.gif' + '" /> ' + after;
}
// Filters a given message
function filterThisMessage(neutralMessage, nick, html_escape) {
var filteredMessage = neutralMessage;
// We encode the HTML special chars
if(html_escape)
filteredMessage = filteredMessage.htmlEnc();
// /me command
filteredMessage = filteredMessage.replace(/((^)|((.+)(>)))(\/me )([^<]+)/, nick + ' $7')
// We replace the smilies text into images
.replace(/(:-?@)($|\s|<)/gi, emoteImage('angry', '$1', '$2'))
.replace(/(:-?\[)($|\s|<)/gi, emoteImage('bat', '$1', '$2'))
.replace(/(\(B\))($|\s|<)/g, emoteImage('beer', '$1', '$2'))
.replace(/((:-?D)|(XD))($|\s|<)/gi, emoteImage('biggrin', '$1', '$4'))
.replace(/(:-?\$)($|\s|<)/gi, emoteImage('blush', '$1', '$2'))
.replace(/(\(Z\))($|\s|<)/g, emoteImage('boy', '$1', '$2'))
.replace(/(\(W\))($|\s|<)/g, emoteImage('brflower', '$1', '$2'))
.replace(/((&lt;\/3)|(\(U\)))($|\s|<)/g, emoteImage('brheart', '$1', '$4'))
.replace(/(\(C\))($|\s|<)/g, emoteImage('coffee', '$1', '$2'))
.replace(/((8-\))|(\(H\)))($|\s|<)/g, emoteImage('coolglasses', '$1', '$4'))
.replace(/(:'-?\()($|\s|<)/gi, emoteImage('cry', '$1', '$2'))
.replace(/(\(%\))($|\s|<)/g, emoteImage('cuffs', '$1', '$2'))
.replace(/(\]:-?&gt;)($|\s|<)/gi, emoteImage('devil', '$1', '$2'))
.replace(/(\(D\))($|\s|<)/g, emoteImage('drink', '$1', '$2'))
.replace(/(@}-&gt;--)($|\s|<)/gi, emoteImage('flower', '$1', '$2'))
.replace(/((:-?\/)|(:-?S))($|\s|<)/gi, emoteImage('frowning', '$1', '$4'))
.replace(/(\(X\))($|\s|<)/g, emoteImage('girl', '$1', '$2'))
.replace(/((&lt;3)|(\(L\)))($|\s|<)/g, emoteImage('heart', '$1', '$4'))
.replace(/(\(}\))($|\s|<)/g, emoteImage('hugleft', '$1', '$2'))
.replace(/(\({\))($|\s|<)/g, emoteImage('hugright', '$1', '$2'))
.replace(/(:-?{})($|\s|<)/gi, emoteImage('kiss', '$1', '$2'))
.replace(/(\(I\))($|\s|<)/g, emoteImage('lamp', '$1', '$2'))
.replace(/(:-?3)($|\s|<)/gi, emoteImage('lion', '$1', '$2'))
.replace(/(\(E\))($|\s|<)/g, emoteImage('mail', '$1', '$2'))
.replace(/(\(S\))($|\s|<)/g, emoteImage('moon', '$1', '$2'))
.replace(/(\(8\))($|\s|<)/g, emoteImage('music', '$1', '$2'))
.replace(/((=-?O)|(:-?O))($|\s|<)/gi, emoteImage('oh', '$1', '$4'))
.replace(/(\(T\))($|\s|<)/g, emoteImage('phone', '$1', '$2'))
.replace(/(\(P\))($|\s|<)/g, emoteImage('photo', '$1', '$2'))
.replace(/(:-?!)($|\s|<)/gi, emoteImage('puke', '$1', '$2'))
.replace(/(\(@\))($|\s|<)/g, emoteImage('pussy', '$1', '$2'))
.replace(/(\(R\))($|\s|<)/g, emoteImage('rainbow', '$1', '$2'))
.replace(/(:-?\))($|\s|<)/gi, emoteImage('smile', '$1', '$2'))
.replace(/(\(\*\))($|\s|<)/g, emoteImage('star', '$1', '$2'))
.replace(/(:-?\|)($|\s|<)/gi, emoteImage('stare', '$1', '$2'))
.replace(/(\(N\))($|\s|<)/g, emoteImage('thumbdown', '$1', '$2'))
.replace(/(\(Y\))($|\s|<)/g, emoteImage('thumbup', '$1', '$2'))
.replace(/(:-?P)($|\s|<)/gi, emoteImage('tongue', '$1', '$2'))
.replace(/(:-?\()($|\s|<)/gi, emoteImage('unhappy', '$1', '$2'))
.replace(/(;-?\))($|\s|<)/gi, emoteImage('wink', '$1', '$2'))
// Text in bold
.replace(/(^|\s|>|\()((\*)([^<>'"\*]+)(\*))($|\s|<|\))/gi, '$1<b>$2</b>$6')
// Italic text
.replace(/(^|\s|>|\()((\/)([^<>'"\/]+)(\/))($|\s|<|\))/gi, '$1<em>$2</em>$6')
// Underlined text
.replace(/(^|\s|>|\()((_)([^<>'"_]+)(_))($|\s|<|\))/gi, '$1<span style="text-decoration: underline;">$2</span>$6');
// Add the links
if(html_escape)
filteredMessage = applyLinks(filteredMessage, 'desktop');
// Filter integratebox links
filteredMessage = filterIntegrateBox(filteredMessage);
return filteredMessage;
}
// Filters a xHTML message to be displayed in Jappix
function filterThisXHTML(code) {
// Allowed elements array
var elements = new Array(
'a',
'abbr',
'acronym',
'address',
'blockquote',
'body',
'br',
'cite',
'code',
'dd',
'dfn',
'div',
'dt',
'em',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'html',
'img',
'kbd',
'li',
'ol',
'p',
'pre',
'q',
'samp',
'span',
'strong',
'title',
'ul',
'var'
);
// Allowed attributes array
var attributes = new Array(
'accesskey',
'alt',
'charset',
'cite',
'class',
'height',
'href',
'hreflang',
'id',
'longdesc',
'profile',
'rel',
'rev',
'src',
'style',
'tabindex',
'title',
'type',
'uri',
'version',
'width',
'xml:lang',
'xmlns'
);
// Remove forbidden elements
$(code).find('html body *').each(function() {
// This element is not authorized
if(!existArrayValue(elements, (this).nodeName.toLowerCase()))
$(this).remove();
});
// Remove forbidden attributes
$(code).find('html body *').each(function() {
// Put a pointer on this element (jQuery way & normal way)
var cSelector = $(this);
var cElement = (this);
// Loop the attributes of the current element
$(cElement.attributes).each(function(index) {
// Read the current attribute
var cAttr = cElement.attributes[index];
var cName = cAttr.name;
var cVal = cAttr.value;
// This attribute is not authorized, or contains JS code
if(!existArrayValue(attributes, cName.toLowerCase()) || ((cVal.toLowerCase()).match(/(^|"|')javascript:/)))
cSelector.removeAttr(cName);
});
});
// Filter some other elements
$(code).find('a').attr('target', '_blank');
return $(code).find('html body').html();
}
/*
Jappix - An open social platform
These are the links JS script for Jappix
-------------------------------------------------
License: dual-licensed under AGPL and MPLv2
Authors: Valérian Saliou, Maranda
Last revision: 26/08/11
*/
// Apply links in a string
function applyLinks(string, mode, style) {
// Special stuffs
var style, target;
// Links style
if(!style)
style = '';
else
style = ' style="' + style + '"';
// Open in new tabs
if(mode != 'xhtml-im')
target = ' target="_blank"';
else
target = '';
// XMPP address
string = string.replace(/(\s|<br \/>|^)(([a-zA-Z0-9\._-]+)@([a-zA-Z0-9\.\/_-]+))(,|\s|$)/gi, '$1<a href="xmpp:$2" target="_blank"' + style + '>$2</a>$5');
// Simple link
string = string.replace(/(\s|<br \/>|^|\()((https?|ftp|file|xmpp|irc|mailto|vnc|webcal|ssh|ldap|smb|magnet|spotify)(:)([^<>'"\s\)]+))/gim, '$1<a href="$2"' + target + style + '>$2</a>');
return string;
}
/*
Jappix - An open social platform
These are the inbox JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 08/06/11
*/
// Opens the inbox popup
function openInbox() {
// Popup HTML content
var html =
'<div class="top">' + _e("Your inbox") + '</div>' +
'<div class="content">' +
'<div class="head inbox-head">' +
'<div class="head-text inbox-head-text">' + _e("Available actions") + '</div>' +
'<div class="head-actions inbox-head-actions">' +
'<a href="#" class="a-delete-messages">' + _e("Clean") + '</a>' +
'<a href="#" class="a-new-message">' + _e("New") + '</a>' +
'<a href="#" class="a-show-messages">' + _e("Received") + '</a>' +
'</div>' +
'</div>' +
'<div class="inbox-results">' +
'<p class="inbox-noresults">' + _e("Your inbox is empty.") + '</p>' +
'<div class="inbox"></div>' +
'</div>' +
'<div class="inbox-new">' +
'<div class="inbox-new-to inbox-new-block search">' +
'<p class="inbox-new-text">' + _e("To") + '</p>' +
'<input name="inbox-new-to-input" class="inbox-new-input inbox-new-to-input" type="text" required="" />' +
'</div>' +
'<div class="inbox-new-topic inbox-new-block">' +
'<p class="inbox-new-text">' + _e("Subject") + '</p>' +
'<input name="inbox-new-subject-input" class="inbox-new-input inbox-new-subject-input" type="text" required="" />' +
'</div>' +
'<div class="inbox-new-body inbox-new-block">' +
'<p class="inbox-new-text">' + _e("Content") + '</p>' +
'<textarea class="inbox-new-textarea" rows="8" cols="60" required=""></textarea>' +
'</div>' +
'<form class="inbox-new-file inbox-new-block" action="./php/file-share.php" method="post" enctype="multipart/form-data">' +
'<p class="inbox-new-text">' + _e("File") + '</p>' +
generateFileShare() +
'</form>' +
'<div class="inbox-new-send inbox-new-block">' +
'<a href="#" class="send one-button talk-images">' + _e("Send message") + '</a>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('inbox', html);
// Associate the events
launchInbox();
// Load the messages
loadInbox();
return false;
}
// Closes the inbox popup
function closeInbox() {
// Destroy the popup
destroyPopup('inbox');
return false;
}
// Opens the message compose tool
function composeInboxMessage(xid) {
// Open things
openInbox();
newInboxMessage();
// Apply XID
$('#inbox .inbox-new-to-input').val(xid);
// Focus to the next item
$(document).oneTime(10, function() {
$('#inbox .inbox-new-subject-input').focus();
});
return false;
}
// Stores the inbox
function storeInbox() {
var iq = new JSJaCIQ();
iq.setType('set');
var query = iq.setQuery(NS_PRIVATE);
var storage = query.appendChild(iq.buildNode('storage', {'xmlns': NS_INBOX}));
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored message
if(explodeThis('_', current, 0) == 'inbox') {
// Get the values
var value = $(XMLFromString(storageDB.getItem(current)));
// Create the storage node
storage.appendChild(iq.buildNode('message', {
'id': value.find('id').text().revertHtmlEnc(),
'from': value.find('from').text().revertHtmlEnc(),
'subject': value.find('subject').text().revertHtmlEnc(),
'status': value.find('status').text().revertHtmlEnc(),
'date': value.find('date').text().revertHtmlEnc(),
'xmlns': NS_INBOX
},
value.find('content').text().revertHtmlEnc()
));
}
}
con.send(iq);
}
// Creates a new normal message
function newInboxMessage() {
// Init
var mPath = '#inbox .';
// Reset the previous buddy search
resetBuddySearch('#inbox .inbox-new-to');
// We switch the divs
$(mPath + 'inbox-results, #inbox .a-new-message, #inbox .a-delete-messages').hide();
$(mPath + 'inbox-new, #inbox .a-show-messages').show();
// We focus on the first input
$(document).oneTime(10, function() {
$(mPath + 'inbox-new-to-input').focus();
});
// We reset some stuffs
cleanNewInboxMessage();
return false;
}
// Cleans the inbox
function cleanNewInboxMessage() {
// Init
var mPath = '#inbox .';
// We reset the forms
$(mPath + 'inbox-new-block:not(form) input, ' + mPath + 'inbox-new textarea').val('').removeClass('please-complete');
$(mPath + 'inbox-new-file a').remove();
$(mPath + 'inbox-new-file input').show();
// We close an eventual opened message
$(mPath + 'message-content').remove();
$(mPath + 'one-message').removeClass('message-reading');
}
// Sends a normal message
function sendInboxMessage(to, subject, body) {
// We send the message
var mess = new JSJaCMessage();
// Main attributes
mess.setTo(to);
mess.setSubject(subject);
mess.setType('normal');
// Any file to attach?
var attached = '#inbox .inbox-new-file a.file';
if(exists(attached))
body += '\n' +
'\n' +
$(attached).attr('data-attachedtitle') + ' - ' + $(attached).attr('data-attachedhref');
// Set body
mess.setBody(body);
con.send(mess, handleErrorReply);
}
// Performs the normal message sender checks
function checkInboxMessage() {
// We get some informations
var mPath = '#inbox ';
var to = $(mPath + '.inbox-new-to-input').val();
var body = $(mPath + '.inbox-new-textarea').val();
var subject = $(mPath + '.inbox-new-subject-input').val();
if(to && body && subject) {
// New array of XID
var xid = new Array(to);
// More than one XID
if(to.indexOf(',') != -1)
xid = to.split(',');
for(i in xid) {
var current = xid[i];
// No current value?
if(!current || current.match(/^(\s+)$/))
continue;
// Edit the XID if needed
current = current.replace(/ /g, '');
current = generateXID(current, 'chat');
// We send the message
sendInboxMessage(current, subject, body);
// We clean the inputs
cleanNewInboxMessage();
logThis('Inbox message sent: ' + current, 3);
}
// Close the inbox
closeInbox();
}
else {
$(mPath + 'input[type="text"], ' + mPath + 'textarea').each(function() {
var current = this;
if(!$(current).val()) {
$(document).oneTime(10, function() {
$(current).addClass('please-complete').focus();
});
}
else
$(current).removeClass('please-complete');
});
}
return false;
}
// Shows the inbox messages
function showInboxMessages() {
// Init
var mPath = '#inbox .';
// We switch the divs
$(mPath + 'inbox-new').hide();
$(mPath + 'inbox-results').show();
// We show a new link in the menu
$(mPath + 'a-show-messages').hide();
$(mPath + 'a-delete-messages').show();
$(mPath + 'a-new-message').show();
// We reset some stuffs
cleanNewInboxMessage();
return false;
}
// Displays a normal message
function displayInboxMessage(from, subject, content, status, id, date) {
// Generate some paths
var inbox = '#inbox .';
var one_message = inbox + 'one-message.' + id;
// Message yet displayed!
if(exists(one_message))
return false;
// Get the nearest element
var stamp = extractStamp(Date.jab2date(date));
var nearest = sortElementByStamp(stamp, '#inbox .one-message');
// Get the buddy name
var name = getBuddyName(from).htmlEnc();
// We generate the html code
var nContent = '<div class="one-message message-' + status + ' ' + id + ' ' + hex_md5(from) + '" data-stamp="' + stamp + '">' +
'<div class="message-head">' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'<div class="message-jid">' + name + '</div>' +
'<div class="message-subject">' + subject.htmlEnc() + '</div>' +
'<div class="message-truncated">' + truncate(noLines(content), 90).htmlEnc() + '</div>' +
'</div>' +
'</div>';
// Display the message
if(nearest == 0)
$(inbox + 'inbox-results .inbox').append(nContent);
else
$('#inbox .one-message[data-stamp="' + nearest + '"]:first').before(nContent);
// Click events
$(one_message + ' .message-head').click(function() {
if(!exists(one_message + ' .message-content'))
revealInboxMessage(id, from, subject, content, name, date, status);
else
hideInboxMessage(id);
return false;
});
// Get the user avatar
getAvatar(from, 'cache', 'true', 'forget');
return true;
}
// Stores an inbox message
function storeInboxMessage(from, subject, content, status, id, date) {
// Initialize the XML data
var xml = '<message><id>' + id.htmlEnc().htmlEnc() + '</id><date>' + date.htmlEnc().htmlEnc() + '</date><from>' + from.htmlEnc().htmlEnc() + '</from><subject>' + subject.htmlEnc().htmlEnc() + '</subject><status>' + status.htmlEnc().htmlEnc() + '</status><content>' + content.htmlEnc().htmlEnc() + '</content>';
// End the XML data
xml += '</message>';
// Store this message!
setDB('inbox', id, xml);
}
// Removes a given normal message
function deleteInboxMessage(id) {
// Remove the message from the inbox
$('#inbox .one-message.' + id).remove();
// Remove the message from the database
removeDB('inbox', id);
// Check the unread messages
checkInboxMessages();
// Store the new inbox
storeInbox();
return false;
}
// Removes all the inbox messages
function purgeInbox() {
// Remove all the messages from the database
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored message
if(explodeThis('_', current, 0) == 'inbox')
removeDB('inbox', explodeThis('_', current, 1));
}
// Prevent the database lag
$(document).oneTime(100, function() {
// Store the new inbox
storeInbox();
// Remove all the messages from the inbox
$('#inbox .one-message').remove();
// Reload the inbox
loadInbox();
});
return false;
}
// Checks if there are new messages to be notified
function checkInboxMessages() {
// Selectors
var inbox_link = '#top-content a.inbox-hidable';
var no_results = '#inbox .inbox-noresults';
// Marker
var has_messages = false;
// Read the number of unread messages
var unread = 0;
// Read the local inbox database
for(var i = 0; i < storageDB.length; i++) {
// Database pointer
var current = storageDB.key(i);
// Check inbox messages
if(explodeThis('_', current, 0) == 'inbox') {
// Read the current status
var status = $(XMLFromString(storageDB.getItem(current))).find('status').text();
// Found an unread message
if(status == 'unread')
unread++;
// Update the marker
has_messages = true;
}
}
// No message?
if(!has_messages)
$(no_results).show();
else
$(no_results).hide();
// Reset notifications
$(inbox_link + ' .notify').remove();
// Any unread message?
if(unread) {
// Notify the user
$(inbox_link).prepend('<div class="notify one-counter" data-counter="' + unread + '">' + unread + '</div>');
// Update the title
updateTitle();
return true;
}
// Anyway, update the title
updateTitle();
return false;
}
// Reveal a normal message content
function revealInboxMessage(id, from, subject, content, name, date, status) {
// Message path
var all_message = '#inbox .one-message';
var one_message = all_message + '.' + id;
var one_content = one_message + ' .message-content';
// We reset all the other messages
$(all_message + ' .message-content').remove();
$(all_message).removeClass('message-reading');
// Message content
var html =
'<div class="message-content">' +
'<div class="message-body">' + filterThisMessage(content, name, true) + '</div>' +
'<div class="message-meta">' +
'<span class="date">' + parseDate(date) + '</span>' +
'<a href="#" class="reply one-button talk-images">' + _e("Reply") + '</a>' +
'<a href="#" class="remove one-button talk-images">' + _e("Delete") + '</a>' +
'<div class="clear">' +
'</div>' +
'</div>';
// Message content
html += '</div>';
$(one_message).append(html).addClass('message-reading');
// Click events
$(one_content + ' a.reply').click(function() {
return replyInboxMessage(id, from, subject, content);
});
$(one_content + ' a.remove').click(function() {
return deleteInboxMessage(id);
});
// Unread message
if(status == 'unread') {
// Update our database
var xml = getDB('inbox', id).replace(/<status>unread<\/status>/i,'<status>read</status>');
setDB('inbox', id, xml);
// Remove the unread class
$(one_message).removeClass('message-unread');
// Send it to the server!
storeInbox();
}
// Check the unread messages
checkInboxMessages();
}
// Hides a normal message content
function hideInboxMessage(id) {
// Define the paths
var inbox = '#inbox .';
var one_message = inbox + 'one-message.' + id;
// Reset this message
$(one_message).removeClass('message-reading');
$(one_message + ' .message-content').remove();
}
// Replies to a given normal message
function replyInboxMessage(id, from, subject, body) {
// We switch to the writing div
newInboxMessage();
// Inbox path
var inbox = '#inbox .';
// Generate the body
var body = '\n' + '____________' + '\n\n' + truncate(body, 120);
// We apply the generated values to the form
$(inbox + 'inbox-new-to-input').val(from);
$(inbox + 'inbox-new-subject-input').val(subject);
$(document).oneTime(10, function() {
$(inbox + 'inbox-new-textarea').val(body).focus().selectRange(1, 0);
});
return false;
}
// Loads the inbox messages
function loadInbox() {
// Read the local database
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored message
if(explodeThis('_', current, 0) == 'inbox') {
// Get the current value
var value = $(XMLFromString(storageDB.getItem(current)));
// Display the current message
displayInboxMessage(
value.find('from').text().revertHtmlEnc(),
value.find('subject').text().revertHtmlEnc(),
value.find('content').text().revertHtmlEnc(),
value.find('status').text().revertHtmlEnc(),
value.find('id').text().revertHtmlEnc(),
value.find('date').text().revertHtmlEnc()
);
}
}
// Check new messages
checkInboxMessages();
}
// Wait event for file attaching
function waitInboxAttach() {
$('#inbox .wait').show();
}
// Success event for file attaching
function handleInboxAttach(responseXML) {
// Data selector
var dData = $(responseXML).find('jappix');
// Process the returned data
if(dData.find('error').size()) {
openThisError(4);
logThis('Error while attaching the file: ' + dData.find('error').text(), 1);
}
else {
// Get the file values
var fName = dData.find('title').text();
var fType = dData.find('type').text();
var fURL = dData.find('href').text();
// Hide the attach link, show the unattach one
$('#inbox .inbox-new-file input').hide();
$('#inbox .inbox-new-file').append('<a class="file ' + encodeQuotes(fileCategory(explodeThis('/', fType, 1))) + ' talk-images" href="' + encodeQuotes(fURL) + '" target="_blank">' + fName.htmlEnc() + '</a><a href="#" class="remove one-button talk-images">' + _e("Remove") + '</a>');
// Set values to the file link
$('#inbox .inbox-new-file a.file').attr('data-attachedtitle', fName)
.attr('data-attachedhref', fURL);
// Click events
$('#inbox .inbox-new-file a.remove').click(function() {
$('#inbox .inbox-new-file a').remove();
$('#inbox .inbox-new-file input').show();
return false;
});
logThis('File attached.', 3);
}
// Reset the attach bubble
$('#inbox .inbox-new-file input[type="file"]').val('');
$('#inbox .wait').hide();
}
// Plugin launcher
function launchInbox() {
// Define the pats
var inbox = '#inbox .';
// Define the buddy search vars
var destination = inbox + 'inbox-new-to';
var dHovered = destination + ' ul li.hovered:first';
// Send the message when enter pressend
$(inbox + 'inbox-new input').keyup(function(e) {
if(e.keyCode == 13) {
if(exists(dHovered))
addBuddySearch(destination, $(dHovered).attr('data-xid'));
else
checkInboxMessage();
}
});
// Buddy search
$(inbox + 'inbox-new-to-input').keyup(function(e) {
if(e.keyCode != 13) {
// New buddy search
if((e.keyCode != 40) && (e.keyCode != 38))
createBuddySearch(destination);
// Navigating with keyboard in the results
arrowsBuddySearch(e, destination);
}
})
// Buddy search lost focus
.blur(function() {
if(!$(destination + ' ul').attr('mouse-hover'))
resetBuddySearch(destination);
})
// Buddy search got focus
.focus(function() {
var value = $(this).val();
// Add a comma at the end
if(value && !value.match(/^(.+)((,)(\s)?)$/))
$(this).val(value + ', ');
});
// Click events
$(inbox + 'a-delete-messages').click(purgeInbox);
$(inbox + 'a-new-message').click(newInboxMessage);
$(inbox + 'a-show-messages').click(showInboxMessages);
$(inbox + 'inbox-new-send a').click(checkInboxMessage);
$(inbox + 'bottom .finish').click(function() {
return closeInbox();
});
// File upload
var attach_options = {
dataType: 'xml',
beforeSubmit: waitInboxAttach,
success: handleInboxAttach
};
// Upload form submit event
$('#inbox .inbox-new-file').submit(function() {
if($('#inbox .wait').is(':hidden') && $('#inbox .inbox-new-file input[type="file"]').val())
$(this).ajaxSubmit(attach_options);
return false;
});
// Upload input change event
$('#inbox .inbox-new-file input[type="file"]').change(function() {
if($('#inbox .wait').is(':hidden') && $(this).val())
$('#inbox .inbox-new-file').ajaxSubmit(attach_options);
return false;
});
}
/*
Jappix - An open social platform
These are the microblog JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda
Last revision: 16/05/13
*/
// Completes arrays of an entry's attached files
function attachedMicroblog(selector, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments) {
if($(selector).attr('title'))
tFName.push($(selector).attr('title'));
else
tFName.push('');
if($(selector).attr('href'))
tFURL.push($(selector).attr('href'));
else
tFURL.push('');
if($(selector).find('link[rel="self"][title="thumb"]:first').attr('href'))
tFThumb.push($(selector).find('link[rel="self"][title="thumb"]:first').attr('href'));
else
tFThumb.push('');
if($(selector).attr('source'))
tFSource.push($(selector).attr('source'));
else
tFSource.push('');
if($(selector).attr('type'))
tFType.push($(selector).attr('type'));
else
tFType.push('');
if($(selector).attr('length'))
tFLength.push($(selector).attr('length'));
else
tFLength.push('');
// Comments?
var comments_href_c = $(selector).find('link[rel="replies"][title="comments_file"]:first').attr('href');
if(comments_href_c && comments_href_c.match(/^xmpp:(.+)\?;node=(.+)/)) {
tFEComments.push(RegExp.$1);
tFNComments.push(decodeURIComponent(RegExp.$2));
}
else {
tFEComments.push('');
tFNComments.push('');
}
}
// Displays a given microblog item
function displayMicroblog(packet, from, hash, mode, way) {
// Get some values
var iParse = $(packet.getNode()).find('items item');
iParse.each(function() {
// Initialize
var tContent, tFiltered, tTime, tDate, tStamp, tBody, tName, tID, tHash, tIndividual, tFEClick;
var tHTMLEscape = false;
// Arrays
var tFName = [];
var tFURL = [];
var tFThumb = [];
var tFSource = [];
var tFType = [];
var tFLength = [];
var tFEComments = [];
var tFNComments = [];
var aFURL = [];
var aFCat = [];
// Get the values
tDate = $(this).find('published').text();
tBody = $(this).find('body').text();
tID = $(this).attr('id');
tName = getBuddyName(from);
tHash = 'update-' + hex_md5(tName + tDate + tID);
// Read attached files with a thumb (place them at first)
$(this).find('link[rel="enclosure"]:has(link[rel="self"][title="thumb"])').each(function() {
attachedMicroblog(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments);
});
// Read attached files without any thumb
$(this).find('link[rel="enclosure"]:not(:has(link[rel="self"][title="thumb"]))').each(function() {
attachedMicroblog(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments);
});
// Get the repeat value
var uRepeat = [$(this).find('author name').text(), explodeThis(':', $(this).find('author uri').text(), 1)];
var uRepeated = false;
if(!uRepeat[0])
uRepeat = [getBuddyName(from), uRepeat[1]];
if(!uRepeat[1])
uRepeat = [uRepeat[0], from];
// Repeated?
if(uRepeat[1] != from)
uRepeated = true;
// Get the comments node
var entityComments, nodeComments;
// Get the comments
var comments_href = $(this).find('link[title="comments"]:first').attr('href');
if(comments_href && comments_href.match(/^xmpp:(.+)\?;node=(.+)/)) {
entityComments = RegExp.$1;
nodeComments = decodeURIComponent(RegExp.$2);
}
// No comments node?
if(!entityComments || !nodeComments) {
entityComments = '';
nodeComments = '';
}
// Get the stamp & time
if(tDate) {
tStamp = extractStamp(Date.jab2date(tDate));
tTime = relativeDate(tDate);
}
else {
tStamp = getTimeStamp();
tTime = '';
}
// Get the item geoloc
var tGeoloc = '';
var sGeoloc = $(this).find('geoloc:first');
var gLat = sGeoloc.find('lat').text();
var gLon = sGeoloc.find('lon').text();
if(gLat && gLon) {
tGeoloc += '<a class="geoloc talk-images" href="http://maps.google.com/?q=' + encodeQuotes(gLat) + ',' + encodeQuotes(gLon) + '" target="_blank">';
// Human-readable name?
var gHuman = humanPosition(
sGeoloc.find('locality').text(),
sGeoloc.find('region').text(),
sGeoloc.find('country').text()
);
if(gHuman)
tGeoloc += gHuman.htmlEnc();
else
tGeoloc += gLat.htmlEnc() + '; ' + gLon.htmlEnc();
tGeoloc += '</a>';
}
// Entry content: HTML, parse!
if($(this).find('content[type="html"]').size()) {
// Filter the xHTML message
tContent = filterThisXHTML(this);
tHTMLEscape = false;
}
// Entry content: Fallback on PLAIN?
if(!tContent) {
tContent = $(this).find('content[type="text"]').text();
if(!tContent) {
// Legacy?
tContent = $(this).find('title:not(source > title)').text();
// Last chance?
if(!tContent)
tContent = tBody;
}
// Trim the content
tContent = trim(tContent);
tHTMLEscape = true;
}
// Any content?
if(tContent) {
// Apply links to message body
tFiltered = filterThisMessage(tContent, tName.htmlEnc(), tHTMLEscape);
// Display the received message
var html = '<div class="one-update update_' + hash + ' ' + tHash + '" data-stamp="' + encodeQuotes(tStamp) + '" data-id="' + encodeQuotes(tID) + '" data-xid="' + encodeQuotes(from) + '">' +
'<div class="' + hash + '">' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'</div>' +
'<div class="body">' +
'<p>';
// Is it a repeat?
if(uRepeated)
html += '<a href="#" class="repeat talk-images" title="' + encodeQuotes(printf(_e("This is a repeat from %s"), uRepeat[0] + ' (' + uRepeat[1] + ')')) + '" onclick="return checkChatCreate(\'' + encodeOnclick(uRepeat[1]) + '\', \'chat\');" data-xid="' + encodeQuotes(uRepeat[1]) + '"></a>';
html += '<b title="' + from + '" class="name">' + tName.htmlEnc() + '</b> <span>' + tFiltered + '</span></p>' +
'<p class="infos">' + tTime + tGeoloc + '</p>';
// Any file to display?
if(tFURL.length)
html += '<p class="file">';
// Generate an array of the files URL
for(var a = 0; a < tFURL.length; a++) {
// Not enough data?
if(!tFURL[a])
continue;
// Push the current URL! (YouTube or file)
if(tFURL[a].match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim)) {
aFURL.push(trim(RegExp.$8));
aFCat.push('youtube');
}
else if(canIntegrateBox(strAfterLast('.', tFURL[a]))) {
aFURL.push(tFURL[a]);
aFCat.push(fileCategory(strAfterLast('.', tFURL[a])));
}
}
// Add each file code
for(var f = 0; f < tFURL.length; f++) {
// Not enough data?
if(!tFURL[f])
continue;
// Get the file type
var tFLink = tFURL[f];
var tFExt = strAfterLast('.', tFLink);
var tFCat = fileCategory(tFExt);
// Youtube video?
if(tFLink.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim)) {
tFLink = trim(RegExp.$8);
tFCat = 'youtube';
}
// Supported image/video/sound
if(canIntegrateBox(tFExt) || (tFCat == 'youtube'))
tFEClick = 'onclick="return applyIntegrateBox(\'' + encodeOnclick(tFLink) + '\', \'' + encodeOnclick(tFCat) + '\', \'' + encodeOnclick(aFURL) + '\', \'' + encodeOnclick(aFCat) + '\', \'' + encodeOnclick(tFEComments) + '\', \'' + encodeOnclick(tFNComments) + '\', \'large\');" ';
else
tFEClick = '';
// Any thumbnail?
if(tFThumb[f])
html += '<a class="thumb" ' + tFEClick + 'href="' + encodeQuotes(tFURL[f]) + '" target="_blank" title="' + encodeQuotes(tFName[f]) + '" data-node="' + encodeQuotes(tFNComments[f]) + '"><img src="' + encodeQuotes(tFThumb[f]) + '" alt="" /></a>';
else
html += '<a class="' + encodeQuotes(tFCat) + ' link talk-images" ' + tFEClick + 'href="' + encodeQuotes(tFURL[f]) + '" target="_blank" data-node="' + encodeQuotes(tFNComments[f]) + '">' + tFName[f].htmlEnc() + '</a>';
}
if(tFURL.length)
html += '</p>';
// It's my own notice, we can remove it!
if(from == getXID())
html += '<a href="#" onclick="return removeMicroblog(\'' + encodeOnclick(tID) + '\', \'' + encodeOnclick(tHash) + '\', \'' + encodeOnclick(entityComments) + '\', \'' + encodeOnclick(nodeComments) + '\');" title="' + _e("Remove this notice") + '" class="mbtool remove talk-images"></a>';
// Notice from another user
else {
// User profile
html += '<a href="#" title="' + _e("View profile") + '" class="mbtool profile talk-images" onclick="return openUserInfos(\'' + encodeOnclick(from) + '\');"></a>';
// If PEP is enabled
if(enabledPEP() && tHTMLEscape)
html += '<a href="#" title="' + _e("Repeat this notice") + '" class="mbtool repost talk-images"></a>';
}
html += '</div><div class="comments-container" data-node="' + encodeQuotes(nodeComments) + '"></div></div>';
// Mixed mode
if((mode == 'mixed') && !exists('.mixed .' + tHash)) {
// Remove the old element
if(way == 'push')
$('#channel .content.mixed .one-update.update_' + hash).remove();
// Get the nearest element
var nearest = sortElementByStamp(tStamp, '#channel .mixed .one-update');
// Append the content at the right position (date relative)
if(nearest == 0)
$('#channel .content.mixed').append(html);
else
$('#channel .one-update[data-stamp="' + nearest + '"]:first').before(html);
// Show the new item
if(way == 'push')
$('#channel .content.mixed .one-update.' + tHash).fadeIn('fast');
else
$('#channel .content.mixed .one-update.' + tHash).show();
// Remove the old notices to make the DOM lighter
var oneUpdate = '#channel .content.mixed .one-update';
if($(oneUpdate).size() > 80)
$(oneUpdate + ':last').remove();
// Click event on avatar/name
$('.mixed .' + tHash + ' .avatar-container, .mixed .' + tHash + ' .body b').click(function() {
getMicroblog(from, hash);
});
}
// Individual mode
tIndividual = '#channel .content.individual.microblog-' + hash;
// Can append individual content?
var can_individual = true;
if($('#channel .top.individual input[name="comments"]').val() && exists(tIndividual + ' .one-update'))
can_individual = false;
if(can_individual && exists(tIndividual) && !exists('.individual .' + tHash)) {
if(mode == 'mixed')
$(tIndividual).prepend(html);
else
$(tIndividual + ' a.more').before(html);
// Show the new item
if(way == 'push')
$('#channel .content.individual .one-update.' + tHash).fadeIn('fast');
else
$('#channel .content.individual .one-update.' + tHash).show();
// Make 'more' link visible
$(tIndividual + ' a.more').css('visibility', 'visible');
// Click event on name (if not me!)
if(from != getXID())
$('.individual .' + tHash + ' .avatar-container, .individual .' + tHash + ' .body b').click(function() {
checkChatCreate(from, 'chat');
});
}
// Apply the click event
$('.' + tHash + ' a.repost:not([data-event="true"])').click(function() {
return publishMicroblog(tContent, tFName, tFURL, tFType, tFLength, tFThumb, uRepeat, entityComments, nodeComments, tFEComments, tFNComments);
})
.attr('data-event', 'true');
// Apply the hover event
if(nodeComments)
$('.' + mode + ' .' + tHash).hover(function() {
showCommentsMicroblog($(this), entityComments, nodeComments, tHash);
}, function() {
if($(this).find('div.comments a.one-comment.loading').size())
$(this).find('div.comments').remove();
});
}
});
// Display the avatar of this buddy
getAvatar(from, 'cache', 'true', 'forget');
}
// Removes a given microblog item
function removeMicroblog(id, hash, pserver, cnode) {
/* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */
// Initialize
var selector = $('.' + hash);
var get_last = false;
// Get the latest item for the mixed mode
if(exists('#channel .content.mixed .' + hash))
get_last = true;
// Remove the item from our DOM
selector.fadeOut('fast', function() {
$(this).remove();
});
// Send the IQ to remove the item (and get eventual error callback)
// Also attempt to remove the comments node.
var retract_iq = new JSJaCIQ();
retract_iq.setType('set');
retract_iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}).appendChild(retract_iq.buildNode('retract', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB})).appendChild(retract_iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
var comm_delete_iq;
if (pserver != '' && cnode != '') {
comm_delete_iq = new JSJaCIQ();
comm_delete_iq.setType('set');
comm_delete_iq.setTo(pserver);
comm_delete_iq.appendNode('pubsub', {'xmlns': 'http://jabber.org/protocol/pubsub#owner'}).appendChild(comm_delete_iq.buildNode('delete', {'node': cnode, 'xmlns': 'http://jabber.org/protocol/pubsub#owner'}));
}
if(get_last) {
if (comm_delete_iq) { con.send(comm_delete_iq); }
con.send(retract_iq, handleRemoveMicroblog);
} else {
if (comm_delete_iq) { con.send(comm_delete_iq); }
con.send(retract_iq, handleErrorReply);
}
return false;
}
// Handles the microblog item removal
function handleRemoveMicroblog(iq) {
// Handle the error reply
handleErrorReply(iq);
// Get the latest item
requestMicroblog(getXID(), '1', false, handleUpdateRemoveMicroblog);
}
// Handles the microblog update
function handleUpdateRemoveMicroblog(iq) {
// Error?
if(iq.getType() == 'error')
return;
// Initialize
var xid = bareXID(getStanzaFrom(iq));
var hash = hex_md5(xid);
// Display the item!
displayMicroblog(iq, xid, hash, 'mixed', 'push');
}
// Gets a given microblog comments node
function getCommentsMicroblog(server, node, id) {
/* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve-requestall */
var iq = new JSJaCIQ();
iq.setType('get');
iq.setID('get_' + genID() + '-' + id);
iq.setTo(server);
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
pubsub.appendChild(iq.buildNode('items', {'node': node, 'xmlns': NS_PUBSUB}));
con.send(iq, handleCommentsMicroblog);
return false;
}
// Handles a microblog comments node items
function handleCommentsMicroblog(iq) {
// Path
var id = explodeThis('-', iq.getID(), 1);
var path = 'div.comments[data-id="' + id + '"] div.comments-content';
// Does not exist?
if(!exists(path))
return false;
// Any error?
if(handleErrorReply(iq)) {
$(path).html('<div class="one-comment loading">' + _e("Could not get the comments!") + '</div>');
return false;
}
// Initialize
var data = iq.getNode();
var server = bareXID(getStanzaFrom(iq));
var node = $(data).find('items:first').attr('node');
var users_xid = [];
var code = '';
// No node?
if(!node)
node = $(data).find('publish:first').attr('node');
// Get the parent microblog item
var parent_select = $('#channel .one-update:has(*[data-node="' + node + '"])');
var parent_data = [parent_select.attr('data-xid'), NS_URN_MBLOG, parent_select.attr('data-id')];
// Get the owner XID
var owner_xid = parent_select.attr('data-xid');
var repeat_xid = parent_select.find('a.repeat').attr('data-xid');
// Must we create the complete DOM?
var complete = true;
if($(path).find('.one-comment.compose').size())
complete = false;
// Add the comment tool
if(complete)
code += '<div class="one-comment compose">' +
'<span class="icon talk-images"></span><input type="text" placeholder="' + _e("Type your comment here...") + '" />' +
'</div>';
// Append the comments
$(data).find('item').each(function() {
// Get comment
var current_id = $(this).attr('id');
var current_xid = explodeThis(':', $(this).find('author uri').text(), 1);
var current_name = $(this).find('author name').text();
var current_date = $(this).find('published').text();
var current_body = $(this).find('content[type="text"]').text();
var current_bname = getBuddyName(current_xid);
// Legacy?
if(!current_body)
current_body = $(this).find('title:not(source > title)').text();
// Yet displayed? (continue the loop)
if($(path).find('.one-comment[data-id="' + current_id + '"]').size())
return;
// No XID?
if(!current_xid) {
current_xid = '';
if(!current_name)
current_name = _e("unknown");
}
else if(!current_name || (current_bname != getXIDNick(current_xid)))
current_name = current_bname;
// Any date?
if(current_date)
current_date = relativeDate(current_date);
else
current_date = getCompleteTime();
// Click event
var onclick = 'false';
if(current_xid != getXID())
onclick = 'checkChatCreate(\'' + encodeOnclick(current_xid) + '\', \'chat\')';
// If this is my comment, add a marker
var type = 'him';
var marker = '';
var remove = '';
if(current_xid == getXID()) {
type = 'me';
marker = '<div class="marker"></div>';
remove = '<a href="#" class="remove" onclick="return removeCommentMicroblog(\'' + encodeOnclick(server) + '\', \'' + encodeOnclick(node) + '\', \'' + encodeOnclick(current_id) + '\');">' + _e("Remove") + '</a>';
}
// New comment?
var new_class = '';
if(!complete)
new_class = ' new';
// Add the comment
if(current_body) {
// Add the XID
if(!existArrayValue(users_xid, current_xid))
users_xid.push(current_xid);
// Add the HTML code
code += '<div class="one-comment ' + hex_md5(current_xid) + ' ' + type + new_class + '" data-id="' + encodeQuotes(current_id) + '">' +
marker +
'<div class="avatar-container" onclick="return ' + onclick + ';">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'<div class="comment-container">' +
'<a href="#" onclick="return ' + onclick + ';" title="' + encodeQuotes(current_xid) + '" class="name">' + current_name.htmlEnc() + '</a>' +
'<span class="date">' + current_date.htmlEnc() + '</span>' +
remove +
'<p class="body">' + filterThisMessage(current_body, current_name, true) + '</p>' +
'</div>' +
'<div class="clear"></div>' +
'</div>';
}
});
// Add the HTML
if(complete) {
$(path).html(code);
// Focus on the compose input
$(document).oneTime(10, function() {
$(path).find('.one-comment.compose input').focus();
});
}
else {
$(path).find('.one-comment.compose').after(code);
// Beautiful effect
$(path).find('.one-comment.new').slideDown('fast', function() {
adaptCommentMicroblog(id);
}).removeClass('new');
}
// Set the good widths
adaptCommentMicroblog(id);
// Get the avatars
for(a in users_xid)
getAvatar(users_xid[a], 'cache', 'true', 'forget');
// Add the owner XID
if(owner_xid && owner_xid.match('@') && !existArrayValue(users_xid, owner_xid))
users_xid.push(owner_xid);
// Add the repeated from XID
if(repeat_xid && repeat_xid.match('@') && !existArrayValue(users_xid, repeat_xid))
users_xid.push(repeat_xid);
// Remove my own XID
removeArrayValue(users_xid, getXID());
// DOM events
if(complete) {
// Update timer
$(path).everyTime('60s', function() {
getCommentsMicroblog(server, node, id);
logThis('Updating comments node: ' + node + ' on ' + server + '...');
});
// Input key event
$(path).find('.one-comment.compose input').placeholder()
.keyup(function(e) {
if((e.keyCode == 13) && $(this).val()) {
// Send the comment!
sendCommentMicroblog($(this).val(), server, node, id, users_xid, parent_data);
// Reset the input value
$(this).val('');
return false;
}
});
}
}
// Shows the microblog comments box
function showCommentsMicroblog(path, entityComments, nodeComments, tHash) {
// Do not display it twice!
if(path.find('div.comments').size())
return;
// Generate an unique ID
var idComments = genID();
// Create comments container
path.find('div.comments-container').append(
'<div class="comments" data-id="' + encodeQuotes(idComments) + '">' +
'<div class="arrow talk-images"></div>' +
'<div class="comments-content">' +
'<a href="#" class="one-comment loading"><span class="icon talk-images"></span>' + _e("Show comments") + '</a>' +
'</div>' +
'</div>'
);
// Click event
path.find('div.comments a.one-comment').click(function() {
// Set loading info
$(this).parent().html('<div class="one-comment loading"><span class="icon talk-images"></span>' + _e("Loading comments...") + '</div>');
// Request comments
getCommentsMicroblog(entityComments, nodeComments, idComments);
// Remove the comments from the DOM if click away
if(tHash) {
$('#channel').off('click');
$('#channel').on('click', function(evt) {
if(!$(evt.target).parents('.' + tHash).size()) {
$('#channel').off('click');
$('#channel .one-update div.comments-content').stopTime();
$('#channel .one-update div.comments').remove();
}
});
}
return false;
});
}
// Sends a comment on a given microblog comments node
function sendCommentMicroblog(value, server, node, id, notifiy_arr, parent_data) {
/* REF: http://xmpp.org/extensions/xep-0060.html#publisher-publish */
// Not enough data?
if(!value || !server || !node)
return false;
// Get some values
var date = getXMPPTime('utc');
var hash = hex_md5(value + date);
// New IQ
var iq = new JSJaCIQ();
iq.setType('set');
iq.setTo(server);
iq.setID('set_' + genID() + '-' + id);
// PubSub main elements
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': node, 'xmlns': NS_PUBSUB}));
var item = publish.appendChild(iq.buildNode('item', {'id': hash, 'xmlns': NS_PUBSUB}));
var entry = item.appendChild(iq.buildNode('entry', {'xmlns': NS_ATOM}));
// Author infos
var author = entry.appendChild(iq.buildNode('author', {'xmlns': NS_ATOM}));
author.appendChild(iq.buildNode('name', {'xmlns': NS_ATOM}, getName()));
author.appendChild(iq.buildNode('uri', {'xmlns': NS_ATOM}, 'xmpp:' + getXID()));
// Create the comment
entry.appendChild(iq.buildNode('content', {'type': 'text', 'xmlns': NS_ATOM}, value));
entry.appendChild(iq.buildNode('published', {'xmlns': NS_ATOM}, date));
con.send(iq);
// Handle this comment!
iq.setFrom(server);
handleCommentsMicroblog(iq);
// Notify users
if(notifiy_arr && notifiy_arr.length) {
// XMPP link to the item
var href = 'xmpp:' + server + '?;node=' + encodeURIComponent(node) + ';item=' + encodeURIComponent(hash);
// Loop!
for(n in notifiy_arr)
sendNotification(notifiy_arr[n], 'comment', href, value, parent_data);
}
return false;
}
// Removes a given microblog comment item
function removeCommentMicroblog(server, node, id) {
/* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */
// Remove the item from our DOM
$('.one-comment[data-id="' + id + '"]').slideUp('fast', function() {
// Get the parent ID
var parent_id = $(this).parents('div.comments').attr('data-id');
// Remove it!
$(this).remove();
// Adapt the width
adaptCommentMicroblog(parent_id);
});
// Send the IQ to remove the item (and get eventual error callback)
var iq = new JSJaCIQ();
iq.setType('set');
iq.setTo(server);
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var retract = pubsub.appendChild(iq.buildNode('retract', {'node': node, 'xmlns': NS_PUBSUB}));
retract.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
con.send(iq);
return false;
}
// Adapts the comment elements width
function adaptCommentMicroblog(id) {
var selector = $('div.comments[data-id="' + id + '"] div.comments-content');
var selector_width = selector.width();
// Change widths
selector.find('.one-comment.compose input').css('width', selector_width - 60);
selector.find('.one-comment .comment-container').css('width', selector_width - 55);
}
// Handles the microblog of an user
function handleMicroblog(iq) {
// Get the from attribute of this IQ
var from = bareXID(getStanzaFrom(iq));
// Define the selector path
var selector = '#channel .top.individual input[name=';
// Is this request still alive?
if(from == $(selector + 'jid]').val()) {
var hash = hex_md5(from);
// Update the items counter
var old_count = parseInt($(selector + 'counter]').val());
$(selector + 'counter]').val(old_count + 20);
// Display the microblog
displayMicroblog(iq, from, hash, 'individual', 'request');
// Hide the waiting icon
if(enabledPEP())
waitMicroblog('sync');
else
waitMicroblog('unsync');
// Hide the 'more items' link?
if($(iq.getNode()).find('item').size() < old_count)
$('#channel .individual a.more').remove();
// Get the comments?
var comments_node = $('#channel .top.individual input[name="comments"]').val();
if(comments_node && comments_node.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) {
// Get the values
var comments_entity = RegExp.$1;
comments_node = decodeURIComponent(RegExp.$2);
// Selectors
var file_link = $('#channel .individual .one-update p.file a[data-node="' + comments_node + '"]');
var entry_link = $('#channel .individual .one-update:has(.comments-container[data-node="' + comments_node + '"])');
// Is it a microblog entry (or a lonely entry file)?
if(entry_link.size()) {
showCommentsMicroblog(entry_link, comments_entity, comments_node);
entry_link.find('a.one-comment').click();
}
// Is it a file?
else if(file_link.size())
file_link.click();
}
}
logThis('Microblog got: ' + from, 3);
}
// Handles the microblog of an user (from roster)
function handleRosterMicroblog(iq) {
// Get the from attribute of this IQ
var from = bareXID(getStanzaFrom(iq));
// Display the microblog
displayMicroblog(iq, from, hex_md5(from), 'mixed', 'push');
}
// Resets the microblog elements
function resetMicroblog() {
// Reset everything
$('#channel .individual .one-update div.comments-content').stopTime();
$('#channel .individual').remove();
$('#channel .mixed').show();
// Hide the waiting icon
if(enabledPEP())
waitMicroblog('sync');
else
waitMicroblog('unsync');
return false;
}
// Gets the user's microblog to check it exists
function getInitMicroblog() {
getMicroblog(getXID(), hex_md5(getXID()), true);
}
// Handles the user's microblog to create it in case of error
function handleInitMicroblog(iq) {
// Any error?
if((iq.getType() == 'error') && $(iq.getNode()).find('item-not-found').size()) {
// The node may not exist, create it!
setupMicroblog('', NS_URN_MBLOG, '1', '1000000', '', '', true);
logThis('Error while getting microblog, trying to reconfigure the PubSub node!', 2);
}
}
// Requests an user's microblog
function requestMicroblog(xid, items, get_item, handler) {
// Ask the server the user's microblog
var iq = new JSJaCIQ();
iq.setType('get');
iq.setTo(xid);
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var ps_items = pubsub.appendChild(iq.buildNode('items', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB}));
// Request a particular item?
if(get_item)
ps_items.appendChild(iq.buildNode('item', {'id': get_item, 'xmlns': NS_PUBSUB}));
else
ps_items.setAttribute('max_items', items);
if(handler)
con.send(iq, handler);
else
con.send(iq, handleMicroblog);
return false;
}
// Gets the microblog of an user
function getMicroblog(xid, hash, check) {
/* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve */
logThis('Get the microblog: ' + xid, 3);
// Fire the wait event
waitMicroblog('fetch');
// XMPP URI?
var get_item = '';
if(xid.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) {
xid = RegExp.$1;
get_item = decodeURIComponent(RegExp.$3);
}
// No hash?
if(!hash)
hash = hex_md5(xid);
// Can display the individual channel?
if(!check && !exists('#channel .individual')) {
// Hide the mixed channel
$('#channel .mixed').hide();
// Get the channel title depending on the XID
var cTitle;
var cShortcuts = '';
if(xid == getXID())
cTitle = _e("Your channel");
else {
cTitle = _e("Channel of") + ' ' + getBuddyName(xid).htmlEnc();
cShortcuts = '<div class="shortcuts">' +
'<a href="#" class="message talk-images" title="' + _e("Send him/her a message") + '" onclick="return composeInboxMessage(\'' + encodeOnclick(xid) + '\');"></a>' +
'<a href="#" class="chat talk-images" title="' + _e("Start a chat with him/her") + '" onclick="return checkChatCreate(\'' + encodeOnclick(xid) + '\', \'chat\');"></a>' +
'<a href="#" class="command talk-images" title="' + _e("Command") + '" onclick="return retrieveAdHoc(\'' + encodeOnclick(xid) + '\');"></a>' +
'<a href="#" class="profile talk-images" title="' + _e("Show user profile") + '" onclick="return openUserInfos(\'' + encodeOnclick(xid) + '\');"></a>' +
'</div>';
}
// Create a new individual channel
$('#channel .content.mixed').after(
'<div class="content individual microblog-' + hash + '">' +
'<a href="#" class="more home-images" onclick="if($(\'#channel .footer div.fetch\').is(\':hidden\')) { return getMicroblog(\'' + encodeOnclick(xid) + '\', \'' + encodeOnclick(hash) + '\'); } return false;">' + _e("More notices...") + '</a>' +
'</div>'
)
.before(
'<div class="top individual ' + hash + '">' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'<div class="update">' +
'<h2>' + cTitle + '</h2>' +
'<a href="#" onclick="return resetMicroblog();">« ' + _e("Previous") + '</a>' +
'</div>' +
cShortcuts +
'<input type="hidden" name="jid" value="' + encodeQuotes(xid) + '" />' +
'<input type="hidden" name="counter" value="20" />' +
'</div>'
);
// Microblog navigation
$('#channel .content.individual').scroll(function() {
if($('#channel .footer div.fetch').is(':hidden') && $('#channel .individual a.more:visible').size() && $('#channel .content.individual').scrollTop() >= ($('#channel .content.individual')[0].scrollHeight - $('#channel .content.individual').height() - 200))
$('#channel .individual a.more').click();
});
// Display the user avatar
getAvatar(xid, 'cache', 'true', 'forget');
}
// Get the number of items to retrieve
var items = '0';
if(!check)
items = $('#channel .top.individual input[name="counter"]').val();
// Request
if(check)
requestMicroblog(xid, items, get_item, handleInitMicroblog);
else
requestMicroblog(xid, items, get_item, handleMicroblog);
return false;
}
// Show a given microblog waiting status
function waitMicroblog(type) {
// First hide all the infos elements
$('#channel .footer div').hide();
// Display the good one
$('#channel .footer div.' + type).show();
// Depending on the type, disable/enable certain tools
var selector = $('#channel .top input[name="microblog_body"]');
if(type == 'unsync')
selector.attr('disabled', true);
else if(type == 'sync')
$(document).oneTime(10, function() {
selector.removeAttr('disabled').focus();
});
}
// Setups a new microblog
function setupMicroblog(entity, node, persist, maximum, access, publish, create) {
/* REF: http://xmpp.org/extensions/xep-0060.html#owner-create-and-configure */
// Create the PubSub node
var iq = new JSJaCIQ();
iq.setType('set');
// Any external entity?
if(entity)
iq.setTo(entity);
// Create it?
if(create) {
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
pubsub.appendChild(iq.buildNode('create', {'xmlns': NS_PUBSUB, 'node': node}));
}
else
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
// Configure it!
var configure = pubsub.appendChild(iq.buildNode('configure', {'node': node, 'xmlns': NS_PUBSUB}));
var x = configure.appendChild(iq.buildNode('x', {'xmlns': NS_XDATA, 'type': 'submit'}));
var field1 = x.appendChild(iq.buildNode('field', {'var': 'FORM_TYPE', 'type': 'hidden', 'xmlns': NS_XDATA}));
field1.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, NS_PUBSUB_NC));
// Persist items?
if(persist) {
var field2 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#persist_items', 'xmlns': NS_XDATA}));
field2.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, persist));
}
// Maximum items?
if(maximum) {
var field3 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#max_items', 'xmlns': NS_XDATA}));
field3.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, maximum));
}
// Access rights?
if(access) {
var field4 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#access_model', 'xmlns': NS_XDATA}));
field4.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, access));
}
// Publish rights?
if(publish) {
var field5 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#publish_model', 'xmlns': NS_XDATA}));
field5.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, publish));
}
con.send(iq);
}
// Gets the microblog configuration
function getConfigMicroblog() {
// Lock the microblog options
$('#persistent, #maxnotices').attr('disabled', true);
// Get the microblog configuration
var iq = new JSJaCIQ();
iq.setType('get');
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
pubsub.appendChild(iq.buildNode('configure', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB_OWNER}));
con.send(iq, handleGetConfigMicroblog);
}
// Handles the microblog configuration
function handleGetConfigMicroblog(iq) {
// Reset the options stuffs
waitOptions('microblog');
// Unlock the microblog options
$('#persistent, #maxnotices').removeAttr('disabled');
// End if not a result
if(!iq || (iq.getType() != 'result'))
return;
// Initialize the values
var selector = $(iq.getNode());
var persistent = '0';
var maxnotices = '1000000';
// Get the values
var xPersistent = selector.find('field[var="pubsub#persist_items"] value:first').text();
var xMaxnotices = selector.find('field[var="pubsub#max_items"] value:first').text();
// Any value?
if(xPersistent)
persistent = xPersistent;
if(xMaxnotices)
maxnotices = xMaxnotices;
// Change the maxnotices value
switch(maxnotices) {
case '1':
case '100':
case '1000':
case '10000':
case '100000':
case '1000000':
break;
default:
maxnotices = '1000000';
break;
}
// Apply persistent value
if(persistent == '0')
$('#persistent').attr('checked', false);
else
$('#persistent').attr('checked', true);
// Apply maxnotices value
$('#maxnotices').val(maxnotices);
}
// Handles the user's microblog
function handleMyMicroblog(packet) {
// Reset the entire form
$('#channel .top input[name="microblog_body"]').removeAttr('disabled').val('');
$('#channel .top input[name="microblog_body"]').placeholder();
unattachMicroblog();
// Check for errors
handleErrorReply(packet);
}
// Performs the microblog sender checks
function sendMicroblog() {
logThis('Send a new microblog item', 3);
// Avoid nasty errors
try {
// Get the values
var selector = $('#channel .top input[name="microblog_body"]');
var body = trim(selector.val());
// Sufficient parameters
if(body) {
// Disable & blur our input
selector.attr('disabled', true).blur();
// Files array
var fName = [];
var fType = [];
var fLength = [];
var fURL = [];
var fThumb = [];
// Read the files
$('#attach .one-file').each(function() {
// Push the values!
fName.push($(this).find('a.link').text());
fType.push($(this).attr('data-type'));
fLength.push($(this).attr('data-length'));
fURL.push($(this).find('a.link').attr('href'));
fThumb.push($(this).attr('data-thumb'));
});
// Containing YouTube videos?
var yt_matches = body.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim);
for(y in yt_matches) {
fName.push('');
fType.push('text/html');
fLength.push('');
fURL.push(trim(yt_matches[y]));
fThumb.push('https://img.youtube.com/vi/' + trim(yt_matches[y].replace(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim, '$8')) + '/0.jpg');
}
// Send the message on the XMPP network
publishMicroblog(body, fName, fURL, fType, fLength, fThumb);
}
}
// Return false (security)
finally {
return false;
}
}
// Publishes a given microblog item
function publishMicroblog(body, attachedname, attachedurl, attachedtype, attachedlength, attachedthumb, repeat, comments_entity, comments_node, comments_entity_file, comments_node_file) {
/* REF: http://xmpp.org/extensions/xep-0277.html */
// Generate some values
var time = getXMPPTime('utc');
var id = hex_md5(body + time);
var nick = getName();
var xid = getXID();
// Define repeat options
var author_nick = nick;
var author_xid = xid;
if(repeat && repeat.length) {
author_nick = repeat[0];
author_xid = repeat[1];
}
// Define comments options
var node_create = false;
if(!comments_entity || !comments_node) {
node_create = true;
comments_entity = HOST_PUBSUB;
comments_node = NS_URN_MBLOG + ':comments/' + id;
}
if(!comments_entity_file)
comments_entity_file = [];
if(!comments_node_file)
comments_node_file = [];
// Don't create another comments node if only 1 file is attached
if(attachedurl && (attachedurl.length == 1) && (!comments_entity_file[0] || !comments_node_file[0])) {
comments_entity_file = [comments_entity];
comments_node_file = [comments_node];
}
// New IQ
var iq = new JSJaCIQ();
iq.setType('set');
iq.setTo(xid);
// Create the main XML nodes/childs
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB}));
var item = publish.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
var entry = item.appendChild(iq.buildNode('entry', {'xmlns': NS_ATOM}));
// Create the XML author childs
var author = entry.appendChild(iq.buildNode('author', {'xmlns': NS_ATOM}));
author.appendChild(iq.buildNode('name', {'xmlns': NS_ATOM}, author_nick));
author.appendChild(iq.buildNode('uri', {'xmlns': NS_ATOM}, 'xmpp:' + author_xid));
// Create the XML entry childs
entry.appendChild(iq.buildNode('content', {'type': 'text', 'xmlns': NS_ATOM}, body));
entry.appendChild(iq.buildNode('published', {'xmlns': NS_ATOM}, time));
entry.appendChild(iq.buildNode('updated', {'xmlns': NS_ATOM}, time));
entry.appendChild(iq.buildNode('link', {
'rel': 'alternate',
'href': 'xmpp:' + xid + '?;node=' + encodeURIComponent(NS_URN_MBLOG) + ';item=' + encodeURIComponent(id),
'xmlns': NS_ATOM
}));
// Create the attached files nodes
for(var i = 0; i < attachedurl.length; i++) {
// Not enough data?
if(!attachedurl[i])
continue;
// Append a new file element
var file = entry.appendChild(iq.buildNode('link', {'xmlns': NS_ATOM, 'rel': 'enclosure', 'href': attachedurl[i]}));
// Add attributes
if(attachedname[i])
file.setAttribute('title', attachedname[i]);
if(attachedtype[i])
file.setAttribute('type', attachedtype[i]);
if(attachedlength[i])
file.setAttribute('length', attachedlength[i]);
// Any thumbnail?
if(attachedthumb[i])
file.appendChild(iq.buildNode('link', {'xmlns': NS_URN_MBLOG, 'rel': 'self', 'title': 'thumb', 'type': attachedtype[i], 'href': attachedthumb[i]}));
// Any comments node?
if(!comments_entity_file[i] || !comments_node_file[i]) {
// Generate values
comments_entity_file[i] = HOST_PUBSUB;
comments_node_file[i] = NS_URN_MBLOG + ':comments/' + hex_md5(attachedurl[i] + attachedname[i] + attachedtype[i] + attachedlength[i] + time);
// Create the node
setupMicroblog(comments_entity_file[i], comments_node_file[i], '1', '1000000', 'open', 'open', true);
}
file.appendChild(iq.buildNode('link', {'xmlns': NS_URN_MBLOG, 'rel': 'replies', 'title': 'comments_file', 'href': 'xmpp:' + comments_entity_file[i] + '?;node=' + encodeURIComponent(comments_node_file[i])}));
}
// Create the comments child
entry.appendChild(iq.buildNode('link', {'xmlns': NS_ATOM, 'rel': 'replies', 'title': 'comments', 'href': 'xmpp:' + comments_entity + '?;node=' + encodeURIComponent(comments_node)}));
// Create the geoloc child
var geoloc_xml = getDB('geolocation', 'now');
if(geoloc_xml) {
// Create two position arrays
var geo_names = ['lat', 'lon', 'country', 'countrycode', 'region', 'postalcode', 'locality', 'street', 'building', 'text', 'uri', 'timestamp'];
var geo_values = parsePosition(XMLFromString(geoloc_xml));
// New geoloc child
var geoloc = entry.appendChild(iq.buildNode('geoloc', {'xmlns': NS_GEOLOC}));
// Append the geoloc content
for(var g = 0; g < geo_names.length; g++) {
if(geo_names[g] && geo_values[g])
geoloc.appendChild(iq.buildNode(geo_names[g], {'xmlns': NS_GEOLOC}, geo_values[g]));
}
}
// Send the IQ
con.send(iq, handleMyMicroblog);
// Create the XML comments PubSub nodes
if(node_create)
setupMicroblog(comments_entity, comments_node, '1', '1000000', 'open', 'open', true);
return false;
}
// Attaches a file to a microblog post
function attachMicroblog() {
// File upload vars
var attach_options = {
dataType: 'xml',
beforeSubmit: waitMicroblogAttach,
success: handleMicroblogAttach
};
// Upload form submit event
$('#attach').submit(function() {
if(!exists('#attach .wait') && $('#attach input[type="file"]').val())
$(this).ajaxSubmit(attach_options);
return false;
});
// Upload input change event
$('#attach input[type="file"]').change(function() {
if(!exists('#attach .wait') && $(this).val())
$('#attach').ajaxSubmit(attach_options);
return false;
});
}
// Unattaches a microblog file
function unattachMicroblog(id) {
// Individual removal?
if(id)
$('#attach .one-file[data-id="' + id + '"]').remove();
else
$('#attach .one-file').remove();
// Must enable the popup again?
if(!exists('#attach .one-file')) {
// Restore the bubble class
$('#attach').addClass('bubble');
// Enable the bubble click events
if(id) {
$('#attach').hide();
showBubble('#attach');
}
else
closeBubbles();
}
return false;
}
// Wait event for file attaching
function waitMicroblogAttach() {
// Append the wait icon
$('#attach input[type="submit"]').after('<div class="wait wait-medium"></div>');
// Lock the bubble
$('#attach').removeClass('bubble');
}
// Success event for file attaching
function handleMicroblogAttach(responseXML) {
// Data selector
var dData = $(responseXML).find('jappix');
// Process the returned data
if(!dData.find('error').size()) {
// Do not allow this bubble to be hidden
$('#attach').removeClass('bubble');
// Get the file values
var fName = dData.find('title').text();
var fType = dData.find('type').text();
var fLength = dData.find('length').text();
var fURL = dData.find('href').text();
var fThumb = dData.find('thumb').text();
// Generate a file ID
var fID = hex_md5(fURL);
// Add this file
$('#attach .attach-subitem').append(
'<div class="one-file" data-type="' + encodeQuotes(fType) + '" data-length="' + encodeQuotes(fLength) + '" data-thumb="' + encodeQuotes(fThumb) + '" data-id="' + fID + '">' +
'<a class="remove talk-images" href="#" title="' + encodeQuotes(_e("Unattach the file")) + '"></a>' +
'<a class="link" href="' + encodeQuotes(fURL) + '" target="_blank">' + fName.htmlEnc() + '</a>' +
'</div>'
);
// Click event
$('#attach .one-file[data-id="' + fID + '"] a.remove').click(function() {
return unattachMicroblog(fID);
});
logThis('File attached.', 3);
}
// Any error?
else {
openThisError(4);
// Unlock the bubble?
if(!exists('#attach .one-file')) {
$('#attach').addClass('bubble').hide();
// Show the bubble again!
showBubble('#attach');
}
logThis('Error while attaching the file: ' + dData.find('error').text(), 1);
}
// Reset the attach bubble
$('#attach input[type="file"]').val('');
$('#attach .wait').remove();
// Focus on the text input
$(document).oneTime(10, function() {
$('#channel .top input[name="microblog_body"]').focus();
});
}
// Shows the microblog of an user from his infos
function fromInfosMicroblog(xid, hash) {
// Renitialize the channel
resetMicroblog();
// Switch to the channel
switchChan('channel');
// Get the microblog
getMicroblog(xid, hash);
}
// Plugin launcher
function launchMicroblog() {
// Keyboard event
$('#channel .top input[name="microblog_body"]').keyup(function(e) {
// Enter pressed: send the microblog notice
if((e.keyCode == 13) && !exists('#attach .wait'))
return sendMicroblog();
})
// Placeholder
.placeholder();
// Microblog file attacher
attachMicroblog();
}
/*
Jappix - An open social platform
These are the music JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 25/04/11
*/
// Opens the music bubble
function openMusic() {
var path = '.music-content';
// Show the music bubble
showBubble(path);
$(document).oneTime(10, function() {
$(path + ' input').focus();
});
return false;
}
// Parses the music search XML
function parseMusic(xml, type) {
var path = '.music-content ';
var content = path + '.list';
var path_type = content + ' .' + type;
// Create the result container
if(!exists(path_type)) {
var code = '<div class="' + type + '"></div>';
if(type == 'local')
$(content).prepend(code);
else
$(content).append(code);
}
// Fill the results
$(xml).find('track').each(function() {
// Parse the XML
var id = $(this).find('id').text();
var title = $(this).find('name').text();
var artist = $(this).find('artist').text();
var source = $(this).find('source').text();
var duration = $(this).find('duration').text();
var uri = $(this).find('url').text();
var mime = $(this).find('type').text();
// No ID?
if(!id)
id = hex_md5(uri);
// No MIME?
if(!mime)
mime = 'audio/ogg';
// Local URL?
if(type == 'local')
uri = generateURL(uri);
// Append the HTML code
$(path_type).append('<a href="#" class="song" data-id="' + id + '">' + title + '</a>');
// Current playing song?
var current_song = $(path_type + ' a[data-id="' + id + '"]');
if(exists('.music-audio[data-id="' + id + '"]'))
current_song.addClass('playing');
// Click event
current_song.click(function() {
return addMusic(id, title, artist, source, duration, uri, mime, type);
});
});
// The search is finished
if(exists(content + ' .jamendo') && exists(content + ' .local')) {
// Get the result values
var jamendo = $(content + ' .jamendo').text();
var local = $(content + ' .local').text();
// Enable the input
$(path + 'input').val('').removeAttr('disabled');
// No result
if(!jamendo && !local)
$(path + '.no-results').show();
// We must put a separator between the categories
if(jamendo && local)
$(content + ' .local').addClass('special');
}
}
// Sends the music search requests
function searchMusic() {
var path = '.music-content ';
// We get the input string
var string = $(path + 'input').val();
// We lock the search input
$(path + 'input').attr('disabled', true);
// We reset the results
$(path + '.list div').remove();
$(path + '.no-results').hide();
// Get the Jamendo results
$.get('./php/music-search.php', {searchquery: string, location: 'jamendo'}, function(data) {
parseMusic(data, 'jamendo');
});
// Get the local results
$.get('./php/music-search.php', {searchquery: string, location: JAPPIX_LOCATION}, function(data) {
parseMusic(data, 'local');
});
}
// Performs an action on the music player
function actionMusic(action) {
try {
// Initialize
var playThis = document.getElementById('top-content').getElementsByTagName('audio')[0];
// Nothing to play, exit
if(!playThis)
return false;
var stopButton = $('#top-content a.stop');
// User play a song
if(action == 'play') {
stopButton.show();
playThis.load();
playThis.play();
playThis.addEventListener('ended', function() {
actionMusic('stop');
}, true);
logThis('Music is now playing.');
}
// User stop the song or the song came to its end
else if(action == 'stop') {
stopButton.hide();
playThis.pause();
$('#top-content .music').removeClass('actived');
$('.music-content .list a').removeClass('playing');
$('.music-audio').remove();
publishMusic();
logThis('Music is now stopped.');
}
}
catch(e) {}
finally {
return false;
}
}
// Publishes the current title over PEP
function publishMusic(title, artist, source, duration, uri) {
// We share the tune on PEP if enabled
if(enabledPEP()) {
/* REF: http://xmpp.org/extensions/xep-0118.html */
var iq = new JSJaCIQ();
iq.setType('set');
// Create the main PubSub nodes
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_TUNE, 'xmlns': NS_PUBSUB}));
var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
var tune = item.appendChild(iq.buildNode('tune', {'xmlns': NS_TUNE}));
// Enough data?
if(title || artist || source || uri) {
// Data array
var nodes = new Array(
'title',
'artist',
'source',
'length',
'uri'
);
var values = new Array(
title,
artist,
source,
length,
uri
);
// Create the children nodes
for(i in nodes) {
if(values[i])
tune.appendChild(iq.buildNode(nodes[i], {'xmlns': NS_TUNE}, values[i]));
}
}
con.send(iq);
logThis('New tune sent: ' + title, 3);
}
}
// Adds a music title to the results
function addMusic(id, title, artist, source, duration, uri, mime, type) {
var path = '.music-content ';
// We remove & create a new audio tag
$('.music-audio').remove();
$(path + '.player').prepend('<audio class="music-audio" type="' + mime + '" data-id="' + id + '" />');
// We apply the new source to the player
if(type == 'jamendo')
$('.music-audio').attr('src', 'http://api.jamendo.com/get2/stream/track/redirect/?id=' + id + '&streamencoding=ogg2');
else
$('.music-audio').attr('src', uri);
// We play the target sound
actionMusic('play');
// We set the actived class
$('#top-content .music').addClass('actived');
// We set a current played track indicator
$(path + '.list a').removeClass('playing');
$(path + 'a[data-id="' + id + '"]').addClass('playing');
// We publish what we listen
publishMusic(title, artist, source, duration, uri);
return false;
}
// Plugin launcher
function launchMusic() {
// When music search string submitted
$('.music-content input').keyup(function(e) {
// Enter : send
if(e.keyCode == 13 && $(this).val())
searchMusic();
// Escape : quit
if(e.keyCode == 27)
closeBubbles();
});
}
/*
Jappix - An open social platform
These are the notification JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 04/05/12
*/
// Resets the notifications alert if no one remaining
function closeEmptyNotifications() {
if(!$('.one-notification').size())
closeBubbles();
}
// Checks if there are pending notifications
function checkNotifications() {
// Define the selectors
var notif = '#top-content .notifications';
var nothing = '.notifications-content .nothing';
var empty = '.notifications-content .empty';
// Get the notifications number
var number = $('.one-notification').size();
// Remove the red notify bubble
$(notif + ' .notify').remove();
// Any notification?
if(number) {
$(notif).prepend('<div class="notify one-counter" data-counter="' + number + '">' + number + '</div>');
$(nothing).hide();
$(empty).show();
}
// No notification!
else {
$(empty).hide();
$(nothing).show();
// Purge the social inbox node
purgeNotifications();
}
// Update the page title
updateTitle();
}
// Creates a new notification
function newNotification(type, from, data, body, id, inverse) {
if(!type || !from)
return;
// Generate an ID hash
if(!id)
var id = hex_md5(type + from);
// Generate the text to be displayed
var text, action, code;
var yes_path = 'href="#"';
// User things
from = bareXID(from);
var hash = hex_md5(from);
switch(type) {
case 'subscribe':
// Get the name to display
var display_name = data[1];
if(!display_name)
display_name = data[0];
text = '<b>' + display_name.htmlEnc() + '</b> ' + _e("would like to add you as a friend.") + ' ' + _e("Do you accept?");
break;
case 'invite_room':
text = '<b>' + getBuddyName(from).htmlEnc() + '</b> ' + _e("would like you to join this chatroom:") + ' <em>' + data[0].htmlEnc() + '</em> ' + _e("Do you accept?");
break;
case 'request':
text = '<b>' + from.htmlEnc() + '</b> ' + _e("would like to get authorization.") + ' ' + _e("Do you accept?");
break;
case 'send':
yes_path = 'href="' + encodeQuotes(data[1]) + '" target="_blank"';
text = '<b>' + getBuddyName(from).htmlEnc() + '</b> ' + printf(_e("would like to send you a file: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>') + ' ' + _e("Do you accept?");
break;
case 'send_pending':
text = '<b>' + getBuddyName(from).htmlEnc() + '</b> ' + printf(_e("has received a file exchange request: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'send_accept':
text = '<b>' + getBuddyName(from).htmlEnc() + '</b> ' + printf(_e("has accepted to receive your file: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'send_reject':
text = '<b>' + getBuddyName(from).htmlEnc() + '</b> ' + printf(_e("has rejected to receive your file: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'send_fail':
text = '<b>' + getBuddyName(from).htmlEnc() + '</b> ' + printf(_e("could not receive your file: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'rosterx':
text = printf(_e("Do you want to see the friends %s suggests you?").htmlEnc(), '<b>' + getBuddyName(from).htmlEnc() + '</b>');
break;
case 'comment':
text = '<b>' + data[0].htmlEnc() + '</b> ' + printf(_e("commented an item you follow: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'like':
text = '<b>' + data[0].htmlEnc() + '</b> ' + printf(_e("liked your post: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'quote':
text = '<b>' + data[0].htmlEnc() + '</b> ' + printf(_e("quoted you somewhere: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'wall':
text = '<b>' + data[0].htmlEnc() + '</b> ' + printf(_e("published on your wall: “%s”.").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'photo':
text = '<b>' + data[0].htmlEnc() + '</b> ' + printf(_e("tagged you in a photo (%s).").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'video':
text = '<b>' + data[0].htmlEnc() + '</b> ' + printf(_e("tagged you in a video (%s).").htmlEnc(), '<em>' + truncate(body, 25).htmlEnc() + '</em>');
break;
case 'me_profile_new_success':
yes_path = 'href="' + encodeQuotes(data[1]) + '" target="_blank"';
text = '<b>' + data[0].htmlEnc() + '</b> ' + _e("validated your account. Your public profile will be available in a few moments.").htmlEnc();
break;
case 'me_profile_remove_success':
yes_path = 'href="' + encodeQuotes(data[1]) + '" target="_blank"';
text = '<b>' + data[0].htmlEnc() + '</b> ' + _e("has removed your public profile after your request. We will miss you!").htmlEnc();
break;
case 'me_profile_update_success':
yes_path = 'href="' + encodeQuotes(data[1]) + '" target="_blank"';
text = '<b>' + data[0].htmlEnc() + '</b> ' + _e("has saved your new public profile settings. They will be applied in a few moments.").htmlEnc();
break;
case 'me_profile_check_error':
yes_path = 'href="' + encodeQuotes(data[1]) + '" target="_blank"';
text = '<b>' + data[0].htmlEnc() + '</b> ' + _e("could not validate your account to create or update your public profile. Check your credentials.").htmlEnc();
break;
default:
break;
}
// No text?
if(!text)
return;
// Action links?
switch(type) {
// Hide/Show actions
case 'send_pending':
case 'send_accept':
case 'send_reject':
case 'send_fail':
case 'comment':
case 'like':
case 'quote':
case 'wall':
case 'photo':
case 'video':
action = '<a href="#" class="no">' + _e("Hide") + '</a>';
// Any parent link?
if((type == 'comment') && data[2])
action = '<a href="#" class="yes">' + _e("Show") + '</a>' + action;
break;
// Jappix Me actions
case 'me_profile_new_success':
case 'me_profile_remove_success':
case 'me_profile_update_success':
case 'me_profile_check_error':
action = '<a ' + yes_path + ' class="yes">' + _e("Open") + '</a><a href="#" class="no">' + _e("Hide") + '</a>';
break;
// Default actions
default:
action = '<a ' + yes_path + ' class="yes">' + _e("Yes") + '</a><a href="#" class="no">' + _e("No") + '</a>';
}
if(text) {
// We display the notification
if(!exists('.notifications-content .' + id)) {
// We create the html markup depending of the notification type
code = '<div class="one-notification ' + id + ' ' + hash + '" title="' + encodeQuotes(body) + ' - ' + _e("This notification is only informative, maybe the data it links to have been removed.") + '" data-type="' + encodeQuotes(type) + '">' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'<p class="notification-text">' + text + '</p>' +
'<p class="notification-actions">' +
'<span class="talk-images" />' +
action +
'</p>' +
'</div>';
// Add the HTML code
if(inverse)
$('.notifications-content .nothing').before(code);
else
$('.notifications-content .empty').after(code);
// Play a sound to alert the user
soundPlay(2);
// The yes click function
$('.' + id + ' a.yes').click(function() {
actionNotification(type, data, 'yes', id);
if(($(this).attr('href') == '#') && ($(this).attr('target') != '_blank'))
return false;
});
// The no click function
$('.' + id + ' a.no').click(function() {
return actionNotification(type, data, 'no', id);
});
// Get the user avatar
getAvatar(from, 'cache', 'true', 'forget');
}
}
// We tell the user he has a new pending notification
checkNotifications();
logThis('New notification: ' + from, 3);
}
// Performs an action on a given notification
function actionNotification(type, data, value, id) {
// We launch a function depending of the type
if((type == 'subscribe') && (value == 'yes'))
acceptSubscribe(data[0], data[1]);
else if((type == 'subscribe') && (value == 'no'))
sendSubscribe(data[0], 'unsubscribed');
else if((type == 'invite_room') && (value == 'yes'))
checkChatCreate(data[0], 'groupchat');
else if(type == 'request')
requestReply(value, data[0]);
if((type == 'send') && (value == 'yes'))
replyOOB(data[0], data[3], 'accept', data[2], data[4]);
else if((type == 'send') && (value == 'no'))
replyOOB(data[0], data[3], 'reject', data[2], data[4]);
else if((type == 'rosterx') && (value == 'yes'))
openRosterX(data[0]);
else if((type == 'comment') || (type == 'like') || (type == 'quote') || (type == 'wall') || (type == 'photo') || (type == 'video')) {
if(value == 'yes') {
// Get the microblog item
fromInfosMicroblog(data[2]);
// Append the marker
$('#channel .top.individual').append('<input type="hidden" name="comments" value="' + encodeQuotes(data[1]) + '" />');
}
removeNotification(data[3]);
}
// We remove the notification
$('.notifications-content .' + id).remove();
// We check if there's any other pending notification
closeEmptyNotifications();
checkNotifications();
return false;
}
// Clear the social notifications
function clearNotifications() {
// Remove notifications
$('.one-notification').remove();
// Refresh
closeEmptyNotifications();
checkNotifications();
return false;
}
// Gets the pending social notifications
function getNotifications() {
var iq = new JSJaCIQ();
iq.setType('get');
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
pubsub.appendChild(iq.buildNode('items', {'node': NS_URN_INBOX, 'xmlns': NS_PUBSUB}));
con.send(iq, handleNotifications);
logThis('Getting social notifications...');
}
// Handles the social notifications
function handleNotifications(iq) {
// Any error?
if((iq.getType() == 'error') && $(iq.getNode()).find('item-not-found').size()) {
// The node may not exist, create it!
setupMicroblog('', NS_URN_INBOX, '1', '1000000', 'whitelist', 'open', true);
logThis('Error while getting social notifications, trying to reconfigure the Pubsub node!', 2);
}
// Selector
var items = $(iq.getNode()).find('item');
// Should we inverse?
var inverse = true;
if(items.size() == 1)
inverse = false;
// Parse notifications
items.each(function() {
// Parse the current item
var current_item = $(this).attr('id');
var current_type = $(this).find('link[rel="via"]:first').attr('title');
var current_href = $(this).find('link[rel="via"]:first').attr('href');
var current_parent_href = $(this).find('link[rel="related"]:first').attr('href');
var current_xid = explodeThis(':', $(this).find('author uri').text(), 1);
var current_name = $(this).find('author name').text();
var current_text = $(this).find('content[type="text"]:first').text();
var current_bname = getBuddyName(current_xid);
var current_id = hex_md5(current_type + current_xid + current_href + current_text);
// Choose the good name!
if(!current_name || (current_bname != getXIDNick(current_xid)))
current_name = current_bname;
// Create it!
newNotification(current_type, current_xid, [current_name, current_href, current_parent_href, current_item], current_text, current_id, inverse);
});
logThis(items.size() + ' social notification(s) got!', 3);
}
// Sends a social notification
function sendNotification(xid, type, href, text, parent) {
// Notification ID
var id = hex_md5(xid + text + getTimeStamp());
// IQ
var iq = new JSJaCIQ();
iq.setType('set');
iq.setTo(xid);
// ATOM content
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_URN_INBOX, 'xmlns': NS_PUBSUB}));
var item = publish.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
var entry = item.appendChild(iq.buildNode('entry', {'xmlns': NS_ATOM}));
// Notification author (us)
var author = entry.appendChild(iq.buildNode('author', {'xmlns': NS_ATOM}));
author.appendChild(iq.buildNode('name', {'xmlns': NS_ATOM}, getName()));
author.appendChild(iq.buildNode('uri', {'xmlns': NS_ATOM}, 'xmpp:' + getXID()));
// Notification content
entry.appendChild(iq.buildNode('published', {'xmlns': NS_ATOM}, getXMPPTime('utc')));
entry.appendChild(iq.buildNode('content', {'type': 'text', 'xmlns': NS_ATOM}, text));
entry.appendChild(iq.buildNode('link', {'rel': 'via', 'title': type, 'href': href, 'xmlns': NS_ATOM}));
// Any parent item?
if(parent && parent[0] && parent[1] && parent[2]) {
// Generate the parent XMPP URI
var parent_href = 'xmpp:' + parent[0] + '?;node=' + encodeURIComponent(parent[1]) + ';item=' + encodeURIComponent(parent[2]);
entry.appendChild(iq.buildNode('link', {'rel': 'related', 'href': parent_href, 'xmlns': NS_ATOM}));
}
con.send(iq);
logThis('Sending a social notification to ' + xid + ' (type: ' + type + ')...');
}
// Removes a social notification
function removeNotification(id) {
var iq = new JSJaCIQ();
iq.setType('set');
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var retract = pubsub.appendChild(iq.buildNode('retract', {'node': NS_URN_INBOX, 'xmlns': NS_PUBSUB}));
retract.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
con.send(iq);
}
// Purge the social notifications
function purgeNotifications() {
var iq = new JSJaCIQ();
iq.setType('set');
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
pubsub.appendChild(iq.buildNode('purge', {'node': NS_URN_INBOX, 'xmlns': NS_PUBSUB_OWNER}));
con.send(iq);
return false;
}
// Adapt the notifications bubble max-height
function adaptNotifications() {
// Process the new height
var max_height = $('#right-content').height() - 22;
// New height too small
if(max_height < 250)
max_height = 250;
// Apply the new height
$('.notifications-content .tools-content-subitem').css('max-height', max_height);
}
// Plugin launcher
function launchNotifications() {
// Adapt the notifications height
adaptNotifications();
}
// Window resize event handler
$(window).resize(adaptNotifications);
/*
Jappix - An open social platform
These are the http-reply JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 16/04/13
*/
// Replies to a HTTP request
function requestReply(value, xml) {
// We parse the xml content
var from = fullXID(getStanzaFrom(xml));
var confirm = $(xml.getNode()).find('confirm');
var xmlns = confirm.attr('xmlns');
var id = confirm.attr('id');
var method = confirm.attr('method');
var url = confirm.attr('url');
// We generate the reply message
var aMsg = new JSJaCMessage();
aMsg.setTo(from);
// If "no"
if(value == 'no') {
aMsg.setType('error');
aMsg.appendNode('error', {'code': '401', 'type': 'auth'});
}
// We set the confirm node
aMsg.appendNode('confirm', {'xmlns': xmlns, 'url': url, 'id': id, 'method': method});
// We send the message
con.send(aMsg, handleErrorReply);
logThis('Replying HTTP auth request: ' + from, 3);
}
/*
Jappix - An open social platform
These are the options JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 07/06/13
*/
// Opens the options popup
function optionsOpen() {
// Popup HTML content
var html =
'<div class="top">' + _e("Edit options") + '</div>' +
'<div class="tab">' +
'<a href="#" class="tab-general tab-active" data-key="1">' + _e("General") + '</a>' +
'<a href="#" class="tab-channel pubsub-hidable pubsub-hidable-cn" data-key="2">' + _e("Channel") + '</a>' +
'<a href="#" class="tab-account" data-key="3">' + _e("Account") + '</a>' +
'</div>' +
'<div class="content">' +
'<div id="conf1" class="lap-active one-lap forms">' +
'<fieldset class="privacy">' +
'<legend>' + _e("Privacy") + '</legend>' +
'<div class="geolocation">' +
'<label for="geolocation" class="pep-hidable">' + _e("Geolocation") + '</label>' +
'<input id="geolocation" type="checkbox" class="pep-hidable" />' +
'</div>' +
'<div class="archiving">' +
'<label for="archiving" class="mam-hidable">' + _e("Message archiving") + '</label>' +
'<select id="archiving" class="mam-hidable">' +
'<option value="never">' + _e("Disabled") + '</option>' +
'<option value="roster">' + _e("Store friend chats") + '</option>' +
'<option value="always">' + _e("Store all chats") + '</option>' +
'</select>' +
'<a href="#" class="linked empty-archives mam-hidable">' + _e("Remove all archives") + '</a>' +
'</div>' +
'</fieldset>' +
'<fieldset class="application">' +
'<legend>' + _e("Application") + '</legend>' +
'<div class="sounds">' +
'<label for="sounds">' + _e("Sounds") + '</label>' +
'<input id="sounds" type="checkbox" />' +
'</div>' +
'<div class="showall">' +
'<label for="showall">' + _e("Show all friends") + '</label>' +
'<input id="showall" type="checkbox" />' +
'</div>' +
'<div class="integratemedias">' +
'<label for="integratemedias">' + _e("Media integration") + '</label>' +
'<input id="integratemedias" type="checkbox" />' +
'</div>' +
'<div class="xmpplinks">' +
'<label class="xmpplinks-hidable">' + _e("XMPP links") + '</label>' +
'<a href="#" class="linked xmpp-links xmpplinks-hidable">' + _e("Open XMPP links with Jappix") + '</a>' +
'</div>' +
'</fieldset>' +
'<div class="sub-ask sub-ask-mam sub-ask-element">' +
'<div class="sub-ask-top">' +
'<div class="sub-ask-title">' + _e("Remove all archives") + '</div>' +
'<a href="#" class="sub-ask-close">X</a>' +
'</div>' +
'<div class="sub-ask-content">' +
'<label>' + _e("Password") + '</label>' +
'<input type="password" class="purge-archives check-mam" required="" />' +
'</div>' +
'<a href="#" class="sub-ask-bottom">' + _e("Remove") + ' &raquo;</a>' +
'</div>' +
'</div>' +
'<div id="conf2" class="one-lap forms">' +
'<fieldset class="channel">' +
'<legend>' + _e("Channel") + '</legend>' +
'<div class="empty-channel">' +
'<label>' + _e("Empty") + '</label>' +
'<a href="#" class="linked empty-channel">' + _e("Empty channel") + '</a>' +
'</div>' +
'<div class="persistent">' +
'<label>' + _e("Persistent") + '</label>' +
'<input id="persistent" type="checkbox" />' +
'</div>' +
'<div class="maxnotices">' +
'<label>' + _e("Maximum notices") + '</label>' +
'<select id="maxnotices">' +
'<option value="1">1</option>' +
'<option value="100">100</option>' +
'<option value="1000">1000</option>' +
'<option value="10000">10000</option>' +
'<option value="100000">100000</option>' +
'<option value="1000000">1000000</option>' +
'</select>' +
'</div>' +
'</fieldset>' +
'<div class="sub-ask sub-ask-empty sub-ask-element">' +
'<div class="sub-ask-top">' +
'<div class="sub-ask-title">' + _e("Empty channel") + '</div>' +
'<a href="#" class="sub-ask-close">X</a>' +
'</div>' +
'<div class="sub-ask-content">' +
'<label>' + _e("Password") + '</label>' +
'<input type="password" class="purge-microblog check-empty" required="" />' +
'</div>' +
'<a href="#" class="sub-ask-bottom">' + _e("Empty") + ' &raquo;</a>' +
'</div>' +
'</div>' +
'<div id="conf3" class="one-lap forms">' +
'<fieldset>' +
'<legend>' + _e("Account") + '</legend>' +
'<label>' + _e("Password") + '</label>' +
'<a href="#" class="linked change-password">' + _e("Change password") + '</a>' +
'<label>' + _e("Delete") + '</label>' +
'<a href="#" class="linked delete-account">' + _e("Delete account") + '</a>' +
'</fieldset>' +
'<div class="sub-ask sub-ask-pass sub-ask-element">' +
'<div class="sub-ask-top">' +
'<div class="sub-ask-title">' + _e("Change password") + '</div>' +
'<a href="#" class="sub-ask-close">X</a>' +
'</div>' +
'<div class="sub-ask-content">' +
'<label>' + _e("Old") + '</label>' +
'<input type="password" class="password-change old" required="" />' +
'<label>' + _e("New (2 times)") + '</label>' +
'<input type="password" class="password-change new1" required="" />' +
'<input type="password" class="password-change new2" required="" />' +
'</div>' +
'<a href="#" class="sub-ask-bottom">' + _e("Continue") + ' &raquo;</a>' +
'</div>' +
'<div class="sub-ask sub-ask-delete sub-ask-element">' +
'<div class="sub-ask-top">' +
'<div class="sub-ask-title">' + _e("Delete account") + '</div>' +
'<a href="#" class="sub-ask-close">X</a>' +
'</div>' +
'<div class="sub-ask-content">' +
'<label>' + _e("Password") + '</label>' +
'<input type="password" class="delete-account check-password" required="" />' +
'</div>' +
'<a href="#" class="sub-ask-bottom">' + _e("Delete") + ' &raquo;</a>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish save">' + _e("Save") + '</a>' +
'<a href="#" class="finish cancel">' + _e("Cancel") + '</a>' +
'</div>';
// Create the popup
createPopup('options', html);
// Apply the features
applyFeatures('options');
// Associate the events
launchOptions();
return false;
}
// Closes the options popup
function closeOptions() {
// Destroy the popup
destroyPopup('options');
return false;
}
// Checks whether the options are loaded or not
function loadedOptions() {
if($('.options-hidable').is(':visible'))
return true;
return false;
}
// Switches between the options tabs
function switchOptions(id) {
$('#options .one-lap').hide();
$('#options #conf' + id).show();
$('#options .tab a').removeClass('tab-active');
$('#options .tab a[data-key="' + id + '"]').addClass('tab-active');
return false;
}
// Manages the options wait item
function waitOptions(id) {
var sOptions = $('#options .content');
// Remove the current item class
sOptions.removeClass(id);
// Hide the waiting items if all was received
if(!sOptions.hasClass('microblog') && !sOptions.hasClass('mam')) {
$('#options .wait').hide();
$('#options .finish:first').removeClass('disabled');
}
}
// Sends the options to the XMPP server
function storeOptions() {
// Get the values
var sounds = getDB('options', 'sounds');
var geolocation = getDB('options', 'geolocation');
var showall = getDB('options', 'roster-showall');
var integratemedias = getDB('options', 'integratemedias');
var status = getDB('options', 'presence-status');
// Create an array to be looped
var oType = new Array('sounds', 'geolocation', 'roster-showall', 'integratemedias', 'presence-status');
var oContent = new Array(sounds, geolocation, showall, integratemedias, status);
// New IQ
var iq = new JSJaCIQ();
iq.setType('set');
var query = iq.setQuery(NS_PRIVATE);
var storage = query.appendChild(iq.buildNode('storage', {'xmlns': NS_OPTIONS}));
// Loop the array
for(i in oType)
storage.appendChild(iq.buildNode('option', {'type': oType[i], 'xmlns': NS_OPTIONS}, oContent[i]));
con.send(iq, handleStoreOptions);
logThis('Storing options...', 3);
}
// Handles the option storing
function handleStoreOptions(iq) {
if(!iq || (iq.getType() != 'result'))
logThis('Options not stored.', 2);
else
logThis('Options stored.', 3);
}
// Saves the user options
function saveOptions() {
// We apply the sounds
var sounds = '0';
if($('#sounds').filter(':checked').size())
sounds = '1';
setDB('options', 'sounds', sounds);
// We apply the geolocation
if($('#geolocation').filter(':checked').size()) {
setDB('options', 'geolocation', '1');
// We geolocate the user on the go
geolocate();
}
else {
setDB('options', 'geolocation', '0');
// We delete the geolocation informations
sendPosition();
removeDB('geolocation', 'now');
}
// We apply the roster show all
if($('#showall').filter(':checked').size()) {
setDB('options', 'roster-showall', '1');
showAllBuddies('options');
}
else {
setDB('options', 'roster-showall', '0');
showOnlineBuddies('options');
}
// We apply the media integration
var integratemedias = '0';
if($('#integratemedias').filter(':checked').size())
integratemedias = '1';
setDB('options', 'integratemedias', integratemedias);
// We apply the message archiving
if(enabledMAM()) {
setConfigMAM($('#archiving').val() || 'never');
}
// We apply the microblog configuration
var persist = '0';
var maximum = $('#maxnotices').val();
if($('#persistent').filter(':checked').size())
persist = '1';
if(enabledPEP() && (enabledPubSub() || enabledPubSubCN()))
setupMicroblog('', NS_URN_MBLOG, persist, maximum, '', '', false);
// We send the options to the database
storeOptions();
// Close the options
closeOptions();
return false;
}
// Handles the password changing
function handlePwdChange(iq) {
// Remove the general wait item
removeGeneralWait();
// If no errors
if(!handleErrorReply(iq)) {
clearLastSession();
quit();
openThisInfo(1);
logThis('Password changed.', 3);
}
else
logThis('Password not changed.', 2);
}
// Sends the new account password
function sendNewPassword() {
/* REF: http://xmpp.org/extensions/xep-0077.html#usecases-changepw */
var password0 = $('#options .old').val();
var password1 = $('#options .new1').val();
var password2 = $('#options .new2').val();
if ((password1 == password2) && (password0 == getPassword())) {
// We show the waiting image
showGeneralWait();
// We send the IQ
var iq = new JSJaCIQ();
iq.setTo(getServer());
iq.setType('set');
var iqQuery = iq.setQuery(NS_REGISTER);
iqQuery.appendChild(iq.buildNode('username', {'xmlns': NS_REGISTER}, con.username));
iqQuery.appendChild(iq.buildNode('password', {'xmlns': NS_REGISTER}, password1));
con.send(iq, handlePwdChange);
logThis('Password change sent.', 3);
} else {
$('.sub-ask-pass input').each(function() {
var select = $(this);
if(!select.val())
$(document).oneTime(10, function() {
select.addClass('please-complete').focus();
});
else
select.removeClass('please-complete');
});
if(password0 != getPassword())
$(document).oneTime(10, function() {
$('#options .old').addClass('please-complete').focus();
});
if(password1 != password2)
$(document).oneTime(10, function() {
$('#options .new1, #options .new2').addClass('please-complete').focus();
});
}
return false;
}
// Handles the account deletion request
function handleAccDeletion(iq) {
// Remove the general wait item
removeGeneralWait();
// If no errors
if(!handleErrorReply(iq)) {
clearLastSession();
destroyTalkPage();
openThisInfo(2);
logout();
logThis('Account deleted.', 3);
} else {
logThis('Account not deleted.', 2);
}
}
// Purge the user's archives (MAM)
function purgeMyArchives() {
var password = $('#options .check-mam').val();
if(password == getPassword()) {
purgeArchivesMAM();
// Clear archives in UI
$('.page-engine-chan[data-type="chat"] .tools-clear').click();
// Hide the tool
$('#options .sub-ask').hide();
} else {
var selector = $('#options .check-mam');
if(password != getPassword())
$(document).oneTime(10, function() {
selector.addClass('please-complete').focus();
});
else
selector.removeClass('please-complete');
}
return false;
}
// Purge the user's microblog items
function purgeMyMicroblog() {
/* REF: http://xmpp.org/extensions/xep-0060.html#owner-purge */
var password = $('#options .check-empty').val();
if(password == getPassword()) {
// Send the IQ to remove the item (and get eventual error callback)
var iq = new JSJaCIQ();
iq.setType('set');
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
pubsub.appendChild(iq.buildNode('purge', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB_OWNER}));
con.send(iq, handleMicroblogPurge);
// Hide the tool
$('#options .sub-ask').hide();
logThis('Microblog purge sent.', 3);
} else {
var selector = $('#options .check-empty');
if(password != getPassword())
$(document).oneTime(10, function() {
selector.addClass('please-complete').focus();
});
else
selector.removeClass('please-complete');
}
return false;
}
// Handles the microblog purge
function handleMicroblogPurge(iq) {
// If no errors
if(!handleErrorReply(iq)) {
// Remove the microblog items
$('.one-update.update_' + hex_md5(getXID())).remove();
logThis('Microblog purged.', 3);
}
else
logThis('Microblog not purged.', 2);
}
// Deletes the user's account
function deleteMyAccount() {
/* REF: http://xmpp.org/extensions/xep-0077.html#usecases-cancel */
var password = $('#options .check-password').val();
if(password == getPassword()) {
// We show the waiting image
showGeneralWait();
// We send the IQ
var iq = new JSJaCIQ();
iq.setType('set');
var iqQuery = iq.setQuery(NS_REGISTER);
iqQuery.appendChild(iq.buildNode('remove', {'xmlns': NS_REGISTER}));
con.send(iq, handleAccDeletion);
logThis('Delete account sent.', 3);
}
else {
var selector = $('#options .check-password');
if(password != getPassword())
$(document).oneTime(10, function() {
selector.addClass('please-complete').focus();
});
else
selector.removeClass('please-complete');
}
return false;
}
// Loads the user options
function loadOptions() {
// Process the good stuffs, depending of the server features
var enabled_mam = enabledMAM();
var enabled_pubsub = enabledPubSub();
var enabled_pubsub_cn = enabledPubSubCN();
var enabled_pep = enabledPEP();
var sWait = $('#options .content');
// Show the waiting items if necessary
if(enabled_mam || (enabled_pep && (enabled_pubsub || enabled_pubsub_cn))) {
$('#options .wait').show();
$('#options .finish:first').addClass('disabled');
}
// We get the archiving configuration
if(enabled_mam) {
sWait.addClass('mam');
getConfigMAM();
}
// We get the microblog configuration
if((enabled_pubsub || enabled_pubsub_cn) && enabled_pep) {
sWait.addClass('microblog');
getConfigMicroblog();
}
// We show the "privacy" form if something is visible into it
if(enabled_mam || enabled_pep)
$('#options fieldset.privacy').show();
// We get the values of the forms for the sounds
if(getDB('options', 'sounds') == '0')
$('#sounds').attr('checked', false);
else
$('#sounds').attr('checked', true);
// We get the values of the forms for the geolocation
if(getDB('options', 'geolocation') == '1')
$('#geolocation').attr('checked', true);
else
$('#geolocation').attr('checked', false);
// We get the values of the forms for the roster show all
if(getDB('options', 'roster-showall') == '1')
$('#showall').attr('checked', true);
else
$('#showall').attr('checked', false);
// We get the values of the forms for the integratemedias
if(getDB('options', 'integratemedias') == '0')
$('#integratemedias').attr('checked', false);
else
$('#integratemedias').attr('checked', true);
}
// Plugin launcher
function launchOptions() {
// Click events
$('#options .tab a').click(function() {
// Yet active?
if($(this).hasClass('tab-active'))
return false;
// Switch to the good tab
var key = parseInt($(this).attr('data-key'));
return switchOptions(key);
});
$('#options .linked').click(function() {
$('#options .sub-ask').hide();
});
$('#options .xmpp-links').click(function() {
xmppLinksHandler();
return false;
});
$('#options .empty-archives').click(function() {
var selector = '#options .sub-ask-mam';
$(selector).show();
$(document).oneTime(10, function() {
$(selector + ' input').focus();
});
return false;
});
$('#options .empty-channel').click(function() {
var selector = '#options .sub-ask-empty';
$(selector).show();
$(document).oneTime(10, function() {
$(selector + ' input').focus();
});
return false;
});
$('#options .change-password').click(function() {
var selector = '#options .sub-ask-pass';
$(selector).show();
$(document).oneTime(10, function() {
$(selector + ' input:first').focus();
});
return false;
});
$('#options .delete-account').click(function() {
var selector = '#options .sub-ask-delete';
$(selector).show();
$(document).oneTime(10, function() {
$(selector + ' input').focus();
});
return false;
});
$('#options .sub-ask-pass .sub-ask-bottom').click(function() {
return sendNewPassword();
});
$('#options .sub-ask-mam .sub-ask-bottom').click(function() {
return purgeMyArchives();
});
$('#options .sub-ask-empty .sub-ask-bottom').click(function() {
return purgeMyMicroblog();
});
$('#options .sub-ask-delete .sub-ask-bottom').click(function() {
return deleteMyAccount();
});
$('#options .sub-ask-close').click(function() {
$('#options .sub-ask').hide();
return false;
});
$('#options .bottom .finish').click(function() {
if($(this).is('.save') && !$(this).hasClass('disabled'))
return saveOptions();
if($(this).is('.cancel'))
return closeOptions();
return false;
});
// The keyup events
$('#options .sub-ask input').keyup(function(e) {
if(e.keyCode == 13) {
// Archives purge
if($(this).is('.purge-archives'))
return purgeMyArchives();
// Microblog purge
else if($(this).is('.purge-microblog'))
return purgeMyMicroblog();
// Password change
else if($(this).is('.password-change'))
return sendNewPassword();
// Account deletion
else if($(this).is('.delete-account'))
return deleteMyAccount();
}
});
// Load the options
loadOptions();
}
/*
Jappix - An open social platform
These are the integratebox JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 03/12/11
*/
// Opens the integratebox popup
function openIntegrateBox() {
// Popup HTML content
var html =
'<div class="top">' + _e("Media viewer") + '</div>' +
'<div class="content"></div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish close">' + _e("Close") + '</a>' +
'<a href="#" class="finish next disabled" title="' + _e("Next") + '">&gt;</a>' +
'<a href="#" class="finish previous disabled" title="' + _e("Previous") + '">&lt;</a>' +
'</div>';
// Create the popup
createPopup('integratebox', html);
// Associate the events
launchIntegratebox();
}
// Closes the integratebox popup
function closeIntegrateBox() {
// Destroy the popup
destroyPopup('integratebox');
return false;
}
// Generates the integratebox HTML code
function codeIntegrateBox(serv, url) {
var code = '';
// Protocol to use
var protocol = 'http';
if(isHTTPS())
protocol = 'https';
// Legacy browser
var legacy = false;
if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9))
legacy = true;
// Switch to get the good DOM code
switch(serv) {
case 'youtube':
if(legacy)
code = '<object width="640" height="385"><param name="movie" value="http://www.youtube.com/v/' + url + '&amp;autoplay=1"></param><embed src="http://www.youtube.com/v/' + encodeQuotes(url) + '&amp;autoplay=1" type="application/x-shockwave-flash" width="640" height="385"></embed></object>';
else
code = '<object width="640" height="385" data="' + encodeQuotes(protocol) + '://www.youtube.com/embed/' + encodeQuotes(url) + '?autoplay=1" type="text/html"><a href="http://www.youtube.com/watch?v=' + encodeQuotes(url) + '" target="_blank">http://www.youtube.com/watch?v=' + encodeQuotes(url) + '</a></object>';
break;
case 'dailymotion':
code = '<object width="640" height="385"><param name="movie" value="http://www.dailymotion.com/swf/video/' + url + '&amp;autoplay=1"></param><param name="allowFullScreen" value="false"></param><embed type="application/x-shockwave-flash" src="http://www.dailymotion.com/swf/video/' + encodeQuotes(url) + '&amp;autoplay=1" width="640" height="385" allowfullscreen="true" allowscriptaccess="always"></embed></object>';
break;
case 'vimeo':
code = '<object width="640" height="385"><param name="allowfullscreen" value="true" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=' + encodeQuotes(url) + '&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1&amp;autoplay=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=' + encodeQuotes(url) + '&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1&amp;autoplay=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="640" height="385"></embed></object>';
break;
case 'theora':
case 'video':
code = '<video width="640" height="385" src="' + encodeQuotes(url) + '" controls autoplay><a href="' + encodeQuotes(url) + '" target="_blank">' + encodeQuotes(url) + '</a></video>';
break;
case 'vorbis':
case 'audio':
code = '<audio src="' + encodeQuotes(url) + '" controls autoplay><a href="' + encodeQuotes(url) + '" target="_blank">' + encodeQuotes(url) + '</a></audio>';
break;
case 'image':
code = '<a href="' + encodeQuotes(url) + '" target="_blank"><img alt="" src="' + encodeQuotes(url) + '" /></a>';
break;
}
return code;
}
// Applies a given integratebox element
function applyIntegrateBox(url, service, url_list, services_list, comments_e_list, comments_n_list, width_style) {
// Close the integratebox
closeIntegrateBox();
// Media integration not wanted?
if(getDB('options', 'integratemedias') == '0')
return true;
// Apply the HTML code
var dom_code = codeIntegrateBox(service, url);
// Any code: apply it!
if(dom_code) {
// We show the integratebox
openIntegrateBox();
// We add the code to the DOM
$('#integratebox .content').prepend('<div class="one-media">' + dom_code + '</div>');
// Image waiting icon
if(service == 'image') {
var waitItem = $('#integratebox .wait');
// Show it while it is loading
waitItem.show();
// Hide it when it is loaded
$('#integratebox img').load(function() {
waitItem.hide();
// Center the image vertically
$(this).oneTime(10, function() {
$(this).css('margin-top', (($('#integratebox .content').height() - $(this).height()) / 2));
});
});
}
// Large style?
var comments_id = genID();
if(width_style == 'large') {
// Make the popup large
$('#integratebox .popup').addClass('large');
// Add the right content
$('#integratebox .content').after(
'<div class="comments" data-id="' + encodeQuotes(comments_id) + '">' +
'<div class="comments-content">' +
'<div class="one-comment loading"><span class="icon talk-images"></span>' + _e("Loading comments...") + '</div>' +
'</div>' +
'</div>'
);
}
// Previous and next items?
var url_array = stringToArray(url_list);
var services_array = stringToArray(services_list);
var comments_e_array = stringToArray(comments_e_list);
var comments_n_array = stringToArray(comments_n_list);
var index = indexArrayValue(url_array, url);
// Any comments?
if(exists('#integratebox .comments')) {
if(comments_e_array[index] && comments_n_array[index])
getCommentsMicroblog(comments_e_array[index], comments_n_array[index], comments_id);
else
$('#integratebox .comments .comments-content').html('<div class="one-comment loading"><span class="icon talk-images"></span>' + _e("Comments locked!") + '</div>');
}
// Get the previous values
var previous_url = url_array[index - 1];
var previous_services = services_array[index - 1];
// Get the next values
var next_url = url_array[index + 1];
var next_services = services_array[index + 1];
// Enable/disable buttons
if(previous_url && previous_services)
$('#integratebox .bottom .finish.previous').removeClass('disabled');
else
$('#integratebox .bottom .finish.previous').addClass('disabled');
if(next_url && next_services)
$('#integratebox .bottom .finish.next').removeClass('disabled');
else
$('#integratebox .bottom .finish.next').addClass('disabled');
// Click events
$('#integratebox .bottom .finish.previous, #integratebox .bottom .finish.next').click(function() {
// Not acceptable?
if($(this).is('.disabled'))
return false;
// Apply the event!
if($(this).is('.previous'))
applyIntegrateBox(previous_url, previous_services, url_list, services_list, comments_e_list, comments_n_list, width_style);
else
applyIntegrateBox(next_url, next_services, url_list, services_list, comments_e_list, comments_n_list, width_style);
return false;
});
if(width_style == 'large')
$('#integratebox .content a:has(img)').click(function() {
if(next_url && next_services)
applyIntegrateBox(next_url, next_services, url_list, services_list, comments_e_list, comments_n_list, width_style);
return false;
});
return false;
}
// Nothing: return true to be able to open the URL in a new tab
return true;
}
// Checks whether the file ext can use integratebox or not
function canIntegrateBox(ext) {
// Can use?
if(ext && ((ext == 'jpg') || (ext == 'jpeg') || (ext == 'png') || (ext == 'gif') || (ext == 'ogg') || (ext == 'oga') || (ext == 'ogv')))
return true;
return false;
}
// Filters a string to apply the integratebox links
function filterIntegrateBox(data) {
// Encapsulates the string into two <div /> elements
var xml = $('<div><div>' + data + '</div></div>').contents();
// Loop the <a /> elements
$(xml).find('a').each(function() {
// Initialize this element
var href = $(this).attr('href');
var to, url, service, event;
// XMPP ID
if(href.match(/^xmpp:(.+)/i))
to = RegExp.$1;
// YouTube video box
else if(href.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim)) {
url = RegExp.$8;
service = 'youtube';
}
// Dailymotion video box
else if(href.match(/(\w{3,5})(:)(\S+)\.dailymotion\.com\/video\/([\w\-]+)((\#[\w\-]+)|\s|$)/gim)) {
url = RegExp.$4;
service = 'dailymotion';
}
// Vimeo video box
else if(href.match(/((\w{3,5})(:)(\S+)(vimeo|www\.vimeo)\.com\/([\w\-]+))/gim)) {
url = RegExp.$1;
service = 'vimeo';
}
// Theora video box
else if(href.match(/((\w{3,5})(:)(\S+)(\.)(ogv|ogg))/gim)) {
url = RegExp.$1;
service = 'theora';
}
// Vorbis audio box
else if(href.match(/((\w{3,5})(:)(\S+)(\.oga))/gim)) {
url = RegExp.$1;
service = 'vorbis';
}
// Image box
else if(href.match(/((\w{3,5})(:)(\S+)(\.)(jpg|jpeg|png|gif|tif|bmp))/gim)) {
url = RegExp.$1;
service = 'image';
}
// Define the good event
if(to)
event = 'xmppLink(\'' + encodeOnclick(to) + '\')';
else if(url && service)
event = 'applyIntegrateBox(\'' + encodeOnclick(url) + '\', \'' + encodeOnclick(service) + '\')';
// Any click event to apply?
if(event) {
// Regenerate the link element (for onclick)
var new_a = '<a';
var element_a = (this);
// Attributes
$(element_a.attributes).each(function(index) {
// Read the current attribute
var current_attr = element_a.attributes[index];
// Apply the current attribute
new_a += ' ' + encodeQuotes(current_attr.name) + '="' + encodeQuotes(current_attr.value) + '"';
});
// Add onclick attribute
new_a += ' onclick="return ' + event + ';"';
// Value
new_a += '>' + $(this).text().htmlEnc() + '</a>';
// Replace it!
$(this).replaceWith(new_a);
}
});
// Regenerate the HTML code (include string into a div to be readable)
var string = $(xml).html();
return string;
}
// Plugin launcher
function launchIntegratebox() {
// Click event
$('#integratebox .bottom .finish.close').click(closeIntegrateBox);
}
// Plugin keyup event
$(document).keyup(function(e) {
// Previous item?
if((exists('#integratebox .bottom .finish.previous:not(.disabled)')) && (e.keyCode == 37)) {
$('#integratebox .bottom .finish.previous').click();
return false;
}
// Next item?
if((exists('#integratebox .bottom .finish.next:not(.disabled)')) && (e.keyCode == 39)) {
$('#integratebox .bottom .finish.next').click();
return false;
}
});
/*
Jappix - An open social platform
These are the PEP JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 26/08/11
*/
// Stores the PEP items
function storePEP(xid, type, value1, value2, value3, value4) {
// Handle the correct values
if(!value1)
value1 = '';
if(!value2)
value2 = '';
if(!value3)
value3 = '';
if(!value4)
value4 = '';
// If one value
if(value1 || value2 || value3 || value4) {
// Define the XML variable
var xml = '<pep type="' + type + '">';
// Generate the correct XML
if(type == 'tune')
xml += '<artist>' + value1.htmlEnc() + '</artist><title>' + value2.htmlEnc() + '</title><album>' + value3.htmlEnc() + '</album><uri>' + value4.htmlEnc() + '</uri>';
else if(type == 'geoloc')
xml += '<lat>' + value1.htmlEnc() + '</lat><lon>' + value2.htmlEnc() + '</lon><human>' + value3.htmlEnc() + '</human>';
else
xml += '<value>' + value1.htmlEnc() + '</value><text>' + value2.htmlEnc() + '</text>';
// End the XML node
xml += '</pep>';
// Update the input with the new value
setDB('pep-' + type, xid, xml);
}
else
removeDB('pep-' + type, xid);
// Display the PEP event
displayPEP(xid, type);
}
// Displays a PEP item
function displayPEP(xid, type) {
// Read the target input for values
var value = $(XMLFromString(getDB('pep-' + type, xid)));
var dText;
var aLink = ''
// If the PEP element exists
if(type) {
// Get the user hash
var hash = hex_md5(xid);
// Initialize
var fText, fValue;
var dText = '';
// Parse the XML for mood and activity
if((type == 'mood') || (type == 'activity')) {
if(value) {
var pepValue = value.find('value').text();
var pepText = value.find('text').text();
// No value?
if(!pepValue)
pepValue = 'none';
// Apply the good values
if(type == 'mood')
fValue = moodIcon(pepValue);
else if(type == 'activity')
fValue = activityIcon(pepValue);
if(!pepText)
fText = _e("unknown");
else
fText = pepText;
}
else {
if(type == 'mood')
fValue = moodIcon('undefined');
else if(type == 'activity')
fValue = activityIcon('exercising');
fText = _e("unknown");
}
dText = fText;
fText = fText.htmlEnc();
}
else if(type == 'tune') {
fValue = 'tune-note';
if(value) {
// Parse the tune XML
var tArtist = value.find('artist').text();
var tTitle = value.find('title').text();
var tAlbum = value.find('album').text();
var tURI = value.find('uri').text();
var fArtist, fTitle, fAlbum, fURI;
// Apply the good values
if(!tArtist && !tAlbum && !tTitle) {
fText = _e("unknown");
dText = fText;
}
else {
// URI element
if(!tURI)
fURI = 'http://grooveshark.com/search?q=' + encodeURIComponent(tArtist + ' ' + tTitle + ' ' + tAlbum);
else
fURI = tURI;
// Artist element
if(!tArtist)
fArtist = _e("unknown");
else
fArtist = tArtist;
// Title element
if(!tTitle)
fTitle = _e("unknown");
else
fTitle = tTitle;
// Album element
if(!tAlbum)
fAlbum = _e("unknown");
else
fAlbum = tAlbum;
// Generate the link to the title
aLink = ' href="' + fURI + '" target="_blank"';
// Generate the text to be displayed
dText = fArtist + ' - ' + fTitle + ' (' + fAlbum + ')';
fText = '<a' + aLink + '>' + dText + '</a>';
}
}
else {
fText = _e("unknown");
dText = fText;
}
}
else if(type == 'geoloc') {
fValue = 'location-world';
if(value) {
// Parse the geoloc XML
var tLat = value.find('lat').text();
var tLon = value.find('lon').text();
var tHuman = value.find('human').text();
var tReal = tHuman;
// No human location?
if(!tHuman)
tHuman = _e("See his/her position on the globe");
// Generate the text to be displayed
if(tLat && tLon) {
aLink = ' href="http://maps.google.com/?q=' + encodeQuotes(tLat) + ',' + encodeQuotes(tLon) + '" target="_blank"';
fText = '<a' + aLink + '>' + tHuman.htmlEnc() + '</a>';
if(tReal)
dText = tReal;
else
dText = tLat + '; ' + tLon;
}
else {
fText = _e("unknown");
dText = fText;
}
}
else {
fText = _e("unknown");
dText = fText;
}
}
// Apply the text to the buddy infos
var this_buddy = '#buddy-list .buddy[data-xid="' + escape(xid) + '"]';
if(exists(this_buddy))
$(this_buddy + ' .bi-' + type).replaceWith('<p class="bi-' + type + ' talk-images ' + fValue + '" title="' + encodeQuotes(dText) + '">' + fText + '</p>');
// Apply the text to the buddy chat
if(exists('#' + hash)) {
// Selector
var bc_pep = $('#' + hash + ' .bc-pep');
// We remove the old PEP item
bc_pep.find('a.bi-' + type).remove();
// If the new PEP item is not null, create a new one
if(fText != _e("unknown"))
bc_pep.prepend(
'<a' + aLink + ' class="bi-' + type + ' talk-images ' + fValue + '" title="' + encodeQuotes(dText) + '"></a>'
);
// Process the new status position
adaptChatPresence(hash);
}
// If this is the PEP values of the logged in user
if(xid == getXID()) {
// Change the icon/value of the target element
if((type == 'mood') || (type == 'activity')) {
// Change the input value
var dVal = '';
var dAttr = pepValue;
// Must apply default values?
if(pepValue == 'none') {
if(type == 'mood')
dAttr = 'happy';
else
dAttr = 'exercising';
}
// No text?
if(dText != _e("unknown"))
dVal = dText;
// Store this user event in our database
setDB(type + '-value', 1, dAttr);
setDB(type + '-text', 1, dVal);
// Apply this PEP event
$('#my-infos .f-' + type + ' a.picker').attr('data-value', dAttr);
$('#my-infos .f-' + type + ' input').val(dVal);
$('#my-infos .f-' + type + ' input').placeholder();
}
else if((type == 'tune') || (type == 'geoloc')) {
// Reset the values
$('#my-infos .f-others a.' + type).remove();
// Not empty?
if(dText != _e("unknown")) {
// Specific stuffs
var href, title, icon_class;
if(type == 'tune') {
href = fURI;
title = dText;
icon_class = 'tune-note';
}
else {
href = 'http://maps.google.com/?q=' + encodeQuotes(tLat) + ',' + encodeQuotes(tLon);
title = _e("Where are you?") + ' (' + dText + ')';
icon_class = 'location-world';
}
// Must create the container?
if(!exists('#my-infos .f-others'))
$('#my-infos .content').append('<div class="element f-others"></div>');
// Create the element
$('#my-infos .f-others').prepend(
'<a class="icon ' + type + '" href="' + encodeQuotes(href) + '" target="_blank" title="' + encodeQuotes(title) + '">' +
'<span class="talk-images ' + icon_class + '"></span>' +
'</a>'
);
}
// Empty?
else if(!exists('#my-infos .f-others a.icon'))
$('#my-infos .f-others').remove();
// Process the buddy-list height again
adaptRoster();
}
}
}
}
// Changes the mood icon
function moodIcon(value) {
// The main var
var icon;
// Switch the values
switch(value) {
case 'angry':
case 'cranky':
case 'hot':
case 'invincible':
case 'mean':
case 'restless':
case 'serious':
case 'strong':
icon = 'mood-one';
break;
case 'contemplative':
case 'happy':
case 'playful':
icon = 'mood-two';
break;
case 'aroused':
case 'envious':
case 'excited':
case 'interested':
case 'lucky':
case 'proud':
case 'relieved':
case 'satisfied':
case 'shy':
icon = 'mood-three';
break;
case 'calm':
case 'cautious':
case 'contented':
case 'creative':
case 'humbled':
case 'lonely':
case 'undefined':
case 'none':
icon = 'mood-four';
break;
case 'afraid':
case 'amazed':
case 'confused':
case 'dismayed':
case 'hungry':
case 'in_awe':
case 'indignant':
case 'jealous':
case 'lost':
case 'offended':
case 'outraged':
case 'shocked':
case 'surprised':
case 'embarrassed':
case 'impressed':
icon = 'mood-five';
break;
case 'crazy':
case 'distracted':
case 'neutral':
case 'relaxed':
case 'thirsty':
icon = 'mood-six';
break;
case 'amorous':
case 'curious':
case 'in_love':
case 'nervous':
case 'sarcastic':
icon = 'mood-eight';
break;
case 'brave':
case 'confident':
case 'hopeful':
case 'grateful':
case 'spontaneous':
case 'thankful':
icon = 'mood-nine';
break;
default:
icon = 'mood-seven';
break;
}
// Return the good icon name
return icon;
}
// Changes the activity icon
function activityIcon(value) {
// The main var
var icon;
// Switch the values
switch(value) {
case 'doing_chores':
icon = 'activity-doing_chores';
break;
case 'drinking':
icon = 'activity-drinking';
break;
case 'eating':
icon = 'activity-eating';
break;
case 'grooming':
icon = 'activity-grooming';
break;
case 'having_appointment':
icon = 'activity-having_appointment';
break;
case 'inactive':
icon = 'activity-inactive';
break;
case 'relaxing':
icon = 'activity-relaxing';
break;
case 'talking':
icon = 'activity-talking';
break;
case 'traveling':
icon = 'activity-traveling';
break;
case 'working':
icon = 'activity-working';
break;
default:
icon = 'activity-exercising';
break;
}
// Return the good icon name
return icon;
}
// Sends the user's mood
function sendMood(value, text) {
/* REF: http://xmpp.org/extensions/xep-0107.html */
// We propagate the mood on the xmpp network
var iq = new JSJaCIQ();
iq.setType('set');
// We create the XML document
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_MOOD, 'xmlns': NS_PUBSUB}));
var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
var mood = item.appendChild(iq.buildNode('mood', {'xmlns': NS_MOOD}));
if(value != 'none') {
mood.appendChild(iq.buildNode(value, {'xmlns': NS_MOOD}));
mood.appendChild(iq.buildNode('text', {'xmlns': NS_MOOD}, text));
}
// And finally we send the mood that is set
con.send(iq);
logThis('New mood sent: ' + value + ' (' + text + ')', 3);
}
// Sends the user's activity
function sendActivity(main, sub, text) {
// We propagate the mood on the xmpp network
var iq = new JSJaCIQ();
iq.setType('set');
// We create the XML document
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_ACTIVITY, 'xmlns': NS_PUBSUB}));
var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
var activity = item.appendChild(iq.buildNode('activity', {'xmlns': NS_ACTIVITY}));
if(main != 'none') {
var mainType = activity.appendChild(iq.buildNode(main, {'xmlns': NS_ACTIVITY}));
// Child nodes
if(sub)
mainType.appendChild(iq.buildNode(sub, {'xmlns': NS_ACTIVITY}));
if(text)
activity.appendChild(iq.buildNode('text', {'xmlns': NS_ACTIVITY}, text));
}
// And finally we send the mood that is set
con.send(iq);
logThis('New activity sent: ' + main + ' (' + text + ')', 3);
}
// Sends the user's geographic position
function sendPosition(vLat, vLon, vAlt, vCountry, vCountrycode, vRegion, vPostalcode, vLocality, vStreet, vBuilding, vText, vURI) {
/* REF: http://xmpp.org/extensions/xep-0080.html */
// We propagate the position on pubsub
var iq = new JSJaCIQ();
iq.setType('set');
// We create the XML document
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_GEOLOC, 'xmlns': NS_PUBSUB}));
var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
var geoloc = item.appendChild(iq.buildNode('geoloc', {'xmlns': NS_GEOLOC}));
// Create two position arrays
var pos_names = ['lat', 'lon', 'alt', 'country', 'countrycode', 'region', 'postalcode', 'locality', 'street', 'building', 'text', 'uri', 'timestamp'];
var pos_values = [ vLat, vLon, vAlt, vCountry, vCountrycode, vRegion, vPostalcode, vLocality, vStreet, vBuilding, vText, vURI, getXMPPTime('utc')];
for(var i = 0; i < pos_names.length; i++) {
if(pos_names[i] && pos_values[i])
geoloc.appendChild(iq.buildNode(pos_names[i], {'xmlns': NS_GEOLOC}, pos_values[i]));
}
// And finally we send the XML
con.send(iq);
// For logger
if(vLat && vLon)
logThis('Geolocated.', 3);
else
logThis('Not geolocated.', 2);
}
// Parses the user's geographic position
function parsePosition(data) {
var result = $(data).find('result:first');
// Get latitude and longitude
var lat = result.find('geometry:first location:first lat').text();
var lng = result.find('geometry:first location:first lng').text();
var array = [
lat,
lng,
result.find('address_component:has(type:contains("country")):first long_name').text(),
result.find('address_component:has(type:contains("country")):first short_name').text(),
result.find('address_component:has(type:contains("administrative_area_level_1")):first long_name').text(),
result.find('address_component:has(type:contains("postal_code")):first long_name').text(),
result.find('address_component:has(type:contains("locality")):first long_name').text(),
result.find('address_component:has(type:contains("route")):first long_name').text(),
result.find('address_component:has(type:contains("street_number")):first long_name').text(),
result.find('formatted_address:first').text(),
'http://maps.google.com/?q=' + encodeQuotes(lat) + ',' + encodeQuotes(lng)
];
return array;
}
// Converts a position into an human-readable one
function humanPosition(tLocality, tRegion, tCountry) {
var tHuman = '';
// Any locality?
if(tLocality) {
tHuman += tLocality;
if(tRegion)
tHuman += ', ' + tRegion;
if(tCountry)
tHuman += ', ' + tCountry;
}
// Any region?
else if(tRegion) {
tHuman += tRegion;
if(tCountry)
tHuman += ', ' + tCountry;
}
// Any country?
else if(tCountry)
tHuman += tCountry;
return tHuman;
}
// Gets the user's geographic position
function getPosition(position) {
// Convert integers to strings
var vLat = '' + position.coords.latitude;
var vLon = '' + position.coords.longitude;
var vAlt = '' + position.coords.altitude;
// Get full position (from Google Maps API)
$.get('./php/geolocation.php', {latitude: vLat, longitude: vLon, language: XML_LANG}, function(data) {
// Parse data!
var results = parsePosition(data);
// Handled!
sendPosition(
isNumber(vLat) ? vLat : null,
isNumber(vLon) ? vLon : null,
isNumber(vAlt) ? vAlt : null,
results[2],
results[3],
results[4],
results[5],
results[6],
results[7],
results[8],
results[9],
results[10]
);
// Store data
setDB('geolocation', 'now', xmlToString(data));
logThis('Position details got from Google Maps API.');
});
logThis('Position got: latitude > ' + vLat + ' / longitude > ' + vLon + ' / altitude > ' + vAlt);
}
// Geolocates the user
function geolocate() {
// Don't fire it until options & features are not retrieved!
if(!getDB('options', 'geolocation') || (getDB('options', 'geolocation') == '0') || !enabledPEP())
return;
// We publish the user location if allowed
if(navigator.geolocation) {
// Wait a bit... (to fix a bug)
$('#my-infos').stopTime().oneTime('1s', function() {
navigator.geolocation.getCurrentPosition(getPosition);
});
logThis('Geolocating...', 3);
}
// Any error?
else
logThis('Not geolocated: browser does not support it.', 1);
}
// Gets the user's microblog to check it exists
function getInitGeoloc() {
var iq = new JSJaCIQ();
iq.setType('get');
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var ps_items = pubsub.appendChild(iq.buildNode('items', {'node': NS_GEOLOC, 'xmlns': NS_PUBSUB}));
ps_items.setAttribute('max_items', '0');
con.send(iq, handleInitGeoloc);
}
// Handles the user's microblog to create it in case of error
function handleInitGeoloc(iq) {
// Any error?
if((iq.getType() == 'error') && $(iq.getNode()).find('item-not-found').size()) {
// The node may not exist, create it!
setupMicroblog('', NS_GEOLOC, '1', '1', '', '', true);
logThis('Error while getting geoloc, trying to reconfigure the PubSub node!', 2);
}
}
// Displays all the supported PEP events for a given XID
function displayAllPEP(xid) {
displayPEP(xid, 'mood');
displayPEP(xid, 'activity');
displayPEP(xid, 'tune');
displayPEP(xid, 'geoloc');
}
// Plugin launcher
function launchPEP() {
// Apply empty values to the PEP database
setDB('mood-value', 1, '');
setDB('mood-text', 1, '');
setDB('activity-value', 1, '');
setDB('activity-text', 1, '');
// Click event for user mood
$('#my-infos .f-mood a.picker').click(function() {
// Initialize some vars
var path = '#my-infos .f-mood div.bubble';
var mood_id = ['crazy', 'excited', 'playful', 'happy', 'shocked', 'hot', 'sad', 'amorous', 'confident'];
var mood_lang = [_e("Crazy"), _e("Excited"), _e("Playful"), _e("Happy"), _e("Shocked"), _e("Hot"), _e("Sad"), _e("Amorous"), _e("Confident")];
var mood_val = $('#my-infos .f-mood a.picker').attr('data-value');
// Yet displayed?
var can_append = true;
if(exists(path))
can_append = false;
// Add this bubble!
showBubble(path);
if(!can_append)
return false;
// Generate the HTML code
var html = '<div class="bubble removable">';
for(i in mood_id) {
// Yet in use: no need to display it!
if(mood_id[i] == mood_val)
continue;
html += '<a href="#" class="talk-images" data-value="' + mood_id[i] + '" title="' + mood_lang[i] + '"></a>';
}
html += '</div>';
// Append the HTML code
$('#my-infos .f-mood').append(html);
// Click event
$(path + ' a').click(function() {
// Update the mood marker
$('#my-infos .f-mood a.picker').attr('data-value', $(this).attr('data-value'));
// Close the bubble
closeBubbles();
// Focus on the status input
$(document).oneTime(10, function() {
$('#mood-text').focus();
});
return false;
});
return false;
});
// Click event for user activity
$('#my-infos .f-activity a.picker').click(function() {
// Initialize some vars
var path = '#my-infos .f-activity div.bubble';
var activity_id = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming', 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling', 'working'];
var activity_lang = [_e("Chores"), _e("Drinking"), _e("Eating"), _e("Exercising"), _e("Grooming"), _e("Appointment"), _e("Inactive"), _e("Relaxing"), _e("Talking"), _e("Traveling"), _e("Working")];
var activity_val = $('#my-infos .f-activity a.picker').attr('data-value');
// Yet displayed?
var can_append = true;
if(exists(path))
can_append = false;
// Add this bubble!
showBubble(path);
if(!can_append)
return false;
// Generate the HTML code
var html = '<div class="bubble removable">';
for(i in activity_id) {
// Yet in use: no need to display it!
if(activity_id[i] == activity_val)
continue;
html += '<a href="#" class="talk-images" data-value="' + activity_id[i] + '" title="' + activity_lang[i] + '"></a>';
}
html += '</div>';
// Append the HTML code
$('#my-infos .f-activity').append(html);
// Click event
$(path + ' a').click(function() {
// Update the activity marker
$('#my-infos .f-activity a.picker').attr('data-value', $(this).attr('data-value'));
// Close the bubble
closeBubbles();
// Focus on the status input
$(document).oneTime(10, function() {
$('#activity-text').focus();
});
return false;
});
return false;
});
// Submit events for PEP inputs
$('#mood-text, #activity-text').placeholder()
.keyup(function(e) {
if(e.keyCode == 13) {
$(this).blur();
return false;
}
});
// Input blur handler
$('#mood-text').blur(function() {
// Read the parameters
var value = $('#my-infos .f-mood a.picker').attr('data-value');
var text = $(this).val();
// Must send the mood?
if((value != getDB('mood-value', 1)) || (text != getDB('mood-text', 1))) {
// Update the local stored values
setDB('mood-value', 1, value);
setDB('mood-text', 1, text);
// Send it!
sendMood(value, text);
}
})
// Input focus handler
.focus(function() {
closeBubbles();
});
// Input blur handler
$('#activity-text').blur(function() {
// Read the parameters
var value = $('#my-infos .f-activity a.picker').attr('data-value');
var text = $(this).val();
// Must send the activity?
if((value != getDB('activity-value', 1)) || (text != getDB('activity-text', 1))) {
// Update the local stored values
setDB('activity-value', 1, value);
setDB('activity-text', 1, text);
// Send it!
sendActivity(value, '', text);
}
})
// Input focus handler
.focus(function() {
closeBubbles();
});
}
/*
Jappix - An open social platform
These are the presence JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 21/06/12
*/
// Sends the user first presence
var FIRST_PRESENCE_SENT = false;
function firstPresence(checksum) {
logThis('First presence sent.', 3);
// Jappix is now ready: change the title
pageTitle('talk');
// Anonymous check
var is_anonymous = isAnonymous();
// Update our marker
FIRST_PRESENCE_SENT = true;
// Try to use the last status message
var status = getDB('options', 'presence-status');
if(!status)
status = '';
// We tell the world that we are online
if(!is_anonymous)
sendPresence('', '', '', status, checksum);
// Any status to apply?
if(status)
$('#presence-status').val(status);
// Enable the presence picker
$('#presence-status').removeAttr('disabled');
$('#my-infos .f-presence a.picker').removeClass('disabled');
// We set the last activity stamp
PRESENCE_LAST_ACTIVITY = getTimeStamp();
// We store our presence
setDB('presence-show', 1, 'available');
// Not anonymous
if(!is_anonymous) {
// We get the stored bookmarks (because of the photo hash and some other stuffs, we must get it later)
getStorage(NS_BOOKMARKS);
// We open a new chat if a XMPP link was submitted
if((parent.location.hash != '#OK') && LINK_VARS['x']) {
// A link is submitted in the URL
xmppLink(LINK_VARS['x']);
// Set a OK status
parent.location.hash = 'OK';
}
}
}
// Handles incoming presence packets
function handlePresence(presence) {
// We define everything needed here
var from = fullXID(getStanzaFrom(presence));
var hash = hex_md5(from);
var node = presence.getNode();
var xid = bareXID(from);
var xidHash = hex_md5(xid);
var resource = thisResource(from);
// We get the type content
var type = presence.getType();
if(!type)
type = '';
// We get the priority content
var priority = presence.getPriority() + '';
if(!priority || (type == 'error'))
priority = '0';
// We get the show content
var show = presence.getShow();
if(!show || (type == 'error'))
show = '';
// We get the status content
var status = presence.getStatus();
if(!status || (type == 'error'))
status = '';
// We get the photo content
var photo = $(node).find('x[xmlns="' + NS_VCARD_P + '"]:first photo');
var checksum = photo.text();
var hasPhoto = photo.size();
if(hasPhoto && (type != 'error'))
hasPhoto = 'true';
else
hasPhoto = 'false';
// We get the CAPS content
var caps = $(node).find('c[xmlns="' + NS_CAPS + '"]:first').attr('ver');
if(!caps || (type == 'error'))
caps = '';
// This presence comes from another resource of my account with a difference avatar checksum
if((xid == getXID()) && (hasPhoto == 'true') && (checksum != getDB('checksum', 1)))
getAvatar(getXID(), 'force', 'true', 'forget');
// This presence comes from a groupchat
if(isPrivate(xid)) {
var x_muc = $(node).find('x[xmlns="' + NS_MUC_USER + '"]:first');
var item = x_muc.find('item');
var affiliation = item.attr('affiliation');
var role = item.attr('role');
var reason = item.find('reason').text();
var iXID = item.attr('jid');
var iNick = item.attr('nick');
var nick = resource;
var messageTime = getCompleteTime();
var notInitial = true;
var resources_obj;
// Read the status code
var status_code = new Array();
x_muc.find('status').each(function() {
status_code.push(parseInt($(this).attr('code')));
});
// If this is an initial presence (when user join the room)
if(exists('#' + xidHash + '[data-initial="true"]'))
notInitial = false;
// If one user is quitting
if(type && (type == 'unavailable')) {
displayMucPresence(from, xidHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, notInitial);
removeDB('presence-stanza', from);
resources_obj = removeResourcePresence(xid, resource);
}
// If one user is joining
else {
// Fixes M-Link first presence bug (missing ID!)
if((nick == getMUCNick(xidHash)) && (presence.getID() == null) && !exists('#page-engine #' + xidHash + ' .list .' + hash)) {
handleMUC(presence);
logThis('Passed M-Link MUC first presence handling.', 2);
}
else {
displayMucPresence(from, xidHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, notInitial);
var xml = '<presence from="' + encodeQuotes(from) + '"><priority>' + priority.htmlEnc() + '</priority><show>' + show.htmlEnc() + '</show><type>' + type.htmlEnc() + '</type><status>' + status.htmlEnc() + '</status><avatar>' + hasPhoto.htmlEnc() + '</avatar><checksum>' + checksum.htmlEnc() + '</checksum><caps>' + caps.htmlEnc() + '</caps></presence>';
setDB('presence-stanza', from, xml);
resources_obj = addResourcePresence(xid, resource);
}
}
// Manage the presence
processPriority(from, resource, resources_obj);
presenceFunnel(from, hash);
}
// This presence comes from an user or a gateway
else {
// Subscribed/Unsubscribed stanzas
if((type == 'subscribed') || (type == 'unsubscribed'))
return;
// Subscribe stanza
else if(type == 'subscribe') {
// This is a buddy we can safely authorize, because we added him to our roster
if(exists('#buddy-list .buddy[data-xid="' + escape(xid) + '"]'))
acceptSubscribe(xid);
// We do not know this entity, we'd be better ask the user
else {
// Get the nickname
var nickname = $(node).find('nick[xmlns="' + NS_NICK + '"]:first').text();
// New notification
newNotification('subscribe', xid, [xid, nickname], status);
}
}
// Unsubscribe stanza
else if(type == 'unsubscribe')
sendRoster(xid, 'remove');
// Other stanzas
else {
var resources_obj;
// Unavailable/error presence
if(type == 'unavailable') {
removeDB('presence-stanza', from);
resources_obj = removeResourcePresence(xid, resource);
}
// Other presence (available, subscribe...)
else {
var xml = '<presence from="' + encodeQuotes(from) + '"><priority>' + priority.htmlEnc() + '</priority><show>' + show.htmlEnc() + '</show><type>' + type.htmlEnc() + '</type><status>' + status.htmlEnc() + '</status><avatar>' + hasPhoto.htmlEnc() + '</avatar><checksum>' + checksum.htmlEnc() + '</checksum><caps>' + caps.htmlEnc() + '</caps></presence>';
setDB('presence-stanza', from, xml);
resources_obj = addResourcePresence(xid, resource);
}
// We manage the presence
processPriority(xid, resource, resources_obj);
presenceFunnel(xid, xidHash);
// We display the presence in the current chat
if(exists('#' + xidHash)) {
var dStatus = filterStatus(xid, status, false);
if(dStatus)
dStatus = ' (' + dStatus + ')';
// Generate the presence-in-chat code
var dName = getBuddyName(from).htmlEnc();
var dBody = dName + ' (' + from + ') ' + _e("is now") + ' ' + humanShow(show, type) + dStatus;
// Check whether it has been previously displayed
var can_display = true;
if($('#' + xidHash + ' .one-line.system-message:last').html() == dBody)
can_display = false;
if(can_display)
displayMessage('chat', xid, xidHash, dName, dBody, getCompleteTime(), getTimeStamp(), 'system-message', false);
}
}
}
// For logger
if(!show) {
if(!type)
show = 'available';
else
show = 'unavailable';
}
logThis('Presence received: ' + show + ', from ' + from);
}
// Displays a MUC presence
function displayMucPresence(from, roomHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, initial) {
// Generate the values
var thisUser = '#page-engine #' + roomHash + ' .list .' + hash;
var thisPrivate = $('#' + hash + ' .message-area');
var nick_html = nick.htmlEnc();
var real_xid = '';
var write = nick_html + ' ';
var notify = false;
// Reset data?
if(!role)
role = 'participant';
if(!affiliation)
affiliation = 'none';
// Must update the role?
if(exists(thisUser) && (($(thisUser).attr('data-role') != role) || ($(thisUser).attr('data-affiliation') != affiliation)))
$(thisUser).remove();
// Any XID submitted?
if(iXID) {
real_xid = ' data-realxid="' + iXID + '"';
iXID = bareXID(iXID);
write += ' (<a onclick="return checkChatCreate(\'' + encodeOnclick(iXID) + '\', \'chat\');" href="xmpp:' + encodeOnclick(iXID) + '">' + iXID + '</a>) ';
}
// User does not exists yet
if(!exists(thisUser) && (!type || (type == 'available'))) {
var myself = '';
// Is it me?
if(nick == getMUCNick(roomHash)) {
// Enable the room
$('#' + roomHash + ' .message-area').removeAttr('disabled');
// Marker
myself = ' myself';
}
// Set the user in the MUC list
$('#' + roomHash + ' .list .' + role + ' .title').after(
'<div class="user ' + hash + myself + '" data-xid="' + encodeQuotes(from) + '" data-nick="' + escape(nick) + '"' + real_xid + ' data-role="' + encodeQuotes(role) + '" data-affiliation="' + encodeQuotes(affiliation) + '">' +
'<div class="name talk-images available">' + nick_html + '</div>' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'</div>'
);
// Click event
if(nick != getMUCNick(roomHash))
$(thisUser).on('click', function() {
checkChatCreate(from, 'private');
});
// We tell the user that someone entered the room
if(!initial) {
notify = true;
write += _e("joined the chat room");
// Any status?
if(status)
write += ' (' + filterThisMessage(status, nick_html, true) + ')';
else
write += ' (' + _e("no status") + ')';
}
// Enable the private chat input
thisPrivate.removeAttr('disabled');
}
else if((type == 'unavailable') || (type == 'error')) {
// Is it me?
if(nick == getMUCNick(roomHash)) {
$(thisUser).remove();
// Disable the groupchat input
$('#' + roomHash + ' .message-area').attr('disabled', true);
// Remove all the groupchat users
$('#' + roomHash + ' .list .user').remove();
}
// Someone has been kicked or banned?
if(existArrayValue(status_code, 301) || existArrayValue(status_code, 307)) {
$(thisUser).remove();
notify = true;
// Kicked?
if(existArrayValue(status_code, 307))
write += _e("has been kicked");
// Banned?
if(existArrayValue(status_code, 301))
write += _e("has been banned");
// Any reason?
if(reason)
write += ' (' + filterThisMessage(reason, nick_html, true) + ')';
else
write += ' (' + _e("no reason") + ')';
}
// Nickname change?
else if(existArrayValue(status_code, 303) && iNick) {
notify = true;
write += printf(_e("changed his/her nickname to %s"), iNick.htmlEnc());
// New values
var new_xid = cutResource(from) + '/' + iNick;
var new_hash = hex_md5(new_xid);
var new_class = 'user ' + new_hash;
if($(thisUser).hasClass('myself'))
new_class += ' myself';
// Die the click event
$(thisUser).off('click');
// Change to the new nickname
$(thisUser).attr('data-nick', escape(iNick))
.attr('data-xid', new_xid)
.find('.name').text(iNick);
// Change the user class
$(thisUser).attr('class', new_class);
// New click event
$('#page-engine #' + roomHash + ' .list .' + new_hash).on('click', function() {
checkChatCreate(new_xid, 'private');
});
}
// We tell the user that someone left the room
else if(!initial) {
$(thisUser).remove();
notify = true;
write += _e("left the chat room");
// Any status?
if(status)
write += ' (' + filterThisMessage(status, nick_html, true) + ')';
else
write += ' (' + _e("no status") + ')';
}
// Disable the private chat input
thisPrivate.attr('disabled', true);
}
// Must notify something
if(notify)
displayMessage('groupchat', from, roomHash, nick_html, write, messageTime, getTimeStamp(), 'system-message', false);
// Set the good status show icon
switch(show) {
case 'chat':
case 'away':
case 'xa':
case 'dnd':
break;
default:
show = 'available';
break;
}
$(thisUser + ' .name').attr('class', 'name talk-images ' + show);
// Set the good status text
var uTitle = nick;
// Any XID to add?
if(iXID)
uTitle += ' (' + iXID + ')';
// Any status to add?
if(status)
uTitle += ' - ' + status;
$(thisUser).attr('title', uTitle);
// Show or hide the role category, depending of its content
$('#' + roomHash + ' .list .role').each(function() {
if($(this).find('.user').size())
$(this).show();
else
$(this).hide();
});
}
// Filters a given status
function filterStatus(xid, status, cut) {
var dStatus = '';
if(!status)
status = '';
else {
if(cut)
dStatus = truncate(status, 50);
else
dStatus = status;
dStatus = filterThisMessage(dStatus, getBuddyName(xid).htmlEnc(), true);
}
return dStatus;
}
// Displays a user's presence
function displayPresence(value, type, show, status, hash, xid, avatar, checksum, caps) {
// Display the presence in the roster
var path = '#buddy-list .' + hash;
var buddy = $('#buddy-list .content .' + hash);
var dStatus = filterStatus(xid, status, false);
var tStatus = encodeQuotes(status);
var biStatus;
// The buddy presence behind his name
$(path + ' .name .buddy-presence').replaceWith('<p class="buddy-presence talk-images ' + type + '">' + value + '</p>');
// The buddy presence in the buddy infos
if(dStatus)
biStatus = dStatus;
else
biStatus = value;
$(path + ' .bi-status').replaceWith('<p class="bi-status talk-images ' + type + '" title="' + tStatus + '">' + biStatus + '</p>');
// When the buddy disconnect himself, we hide him
if((type == 'unavailable') || (type == 'error')) {
// Set a special class to the buddy
buddy.addClass('hidden-buddy');
// No filtering is launched?
if(!SEARCH_FILTERED)
buddy.hide();
// All the buddies are shown?
if(BLIST_ALL)
buddy.show();
// Chat stuffs
if(exists('#' + hash)) {
// Remove the chatstate stuffs
resetChatState(hash);
$('#' + hash + ' .chatstate').remove();
$('#' + hash + ' .message-area').removeAttr('data-chatstates');
// Get the buddy avatar (only if a chat is opened)
getAvatar(xid, 'cache', 'true', 'forget');
}
}
// If the buddy is online
else {
// When the buddy is online, we show it
buddy.removeClass('hidden-buddy');
// No filtering is launched?
if(!SEARCH_FILTERED)
buddy.show();
// Get the online buddy avatar if not a gateway
getAvatar(xid, 'cache', avatar, checksum);
}
// Display the presence in the chat
if(exists('#' + hash)) {
// We generate a well formed status message
if(dStatus) {
// No need to write the same status two times
if(dStatus == value)
dStatus = '';
else
dStatus = ' (' + dStatus + ')';
}
// We show the presence value
$('#' + hash + ' .bc-infos').replaceWith('<p class="bc-infos" title="' + tStatus + '"><span class="' + type + ' show talk-images">' + value + '</span>' + dStatus + '</p>');
// Process the new status position
adaptChatPresence(hash);
// Get the disco#infos for this user
var highest = highestPriority(xid);
if(highest)
getDiscoInfos(highest, caps);
else
displayDiscoInfos(xid, '');
}
// Display the presence in the switcher
if(exists('#page-switch .' + hash))
$('#page-switch .' + hash + ' .icon').removeClass('available unavailable error away busy').addClass(type);
// Update roster groups
if(!SEARCH_FILTERED)
updateGroups();
else
funnelFilterBuddySearch();
}
// Process the chat presence position
function adaptChatPresence(hash) {
// Get values
var pep_numb = $('#' + hash + ' .bc-pep').find('a').size();
// Process the left/right position
var presence_h = 12;
if(pep_numb)
presence_h = (pep_numb * 20) + 18;
// Apply the left/right position
var presence_h_tag = ($('html').attr('dir') == 'rtl') ? 'left' : 'right';
$('#' + hash + ' p.bc-infos').css(presence_h_tag, presence_h);
}
// Convert the presence "show" element into a human-readable output
function humanShow(show, type) {
if(type == 'unavailable')
show = _e("Unavailable");
else if(type == 'error')
show = _e("Error");
else {
switch(show) {
case 'chat':
show = _e("Talkative");
break;
case 'away':
show = _e("Away");
break;
case 'xa':
show = _e("Not available");
break;
case 'dnd':
show = _e("Busy");
break;
default:
show = _e("Available");
break;
}
}
return show;
}
// Makes the presence data go in the right way
function presenceIA(type, show, status, hash, xid, avatar, checksum, caps) {
// Is there a status defined?
if(!status)
status = humanShow(show, type);
// Then we can handle the events
if(type == 'error')
displayPresence(_e("Error"), 'error', show, status, hash, xid, avatar, checksum, caps);
else if(type == 'unavailable')
displayPresence(_e("Unavailable"), 'unavailable', show, status, hash, xid, avatar, checksum, caps);
else {
switch(show) {
case 'chat':
displayPresence(_e("Talkative"), 'available', show, status, hash, xid, avatar, checksum, caps);
break;
case 'away':
displayPresence(_e("Away"), 'away', show, status, hash, xid, avatar, checksum, caps);
break;
case 'xa':
displayPresence(_e("Not available"), 'busy', show, status, hash, xid, avatar, checksum, caps);
break;
case 'dnd':
displayPresence(_e("Busy"), 'busy', show, status, hash, xid, avatar, checksum, caps);
break;
default:
displayPresence(_e("Available"), 'available', show, status, hash, xid, avatar, checksum, caps);
break;
}
}
}
// Flush the presence data for a given user
function flushPresence(xid) {
var flushed_marker = false;
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored presence
if(explodeThis('_', current, 0) == 'presence') {
// Get the current XID
var now_full = explodeThis('_', current, 1);
var now_bare = bareXID(now_full);
// If the current XID equals the asked XID
if(now_bare == xid) {
if(removeDB('presence-stanza', now_full)) {
logThis('Presence data flushed for: ' + now_full, 3);
flushed_marker = true;
i--;
}
}
}
}
return flushed_marker;
}
// Process the highest resource priority for an user
function processPriority(xid, resource, resources_obj) {
try {
if(!xid) {
logThis('processPriority > No XID value', 2);
return;
}
// Initialize vars
var cur_resource, cur_from, cur_pr,
cur_xml, cur_priority,
from_highest, from_highest;
from_highest = null;
max_priority = null;
// Groupchat presence? (no priority here)
if(xid.indexOf('/') !== -1) {
from_highest = xid;
logThis('Processed presence for groupchat user: ' + xid);
} else {
if(!highestPriority(xid)) {
from_highest = xid + '/' + resource;
logThis('Processed initial presence for regular user: ' + xid + ' (highest priority for: ' + (from_highest || 'none') + ')');
} else {
for(cur_resource in resources_obj) {
// Read presence data
cur_from = xid + '/' + cur_resource;
cur_pr = getDB('presence-stanza', cur_from);
if(cur_pr) {
// Parse presence data
cur_xml = XMLFromString(cur_pr);
cur_priority = $(cur_xml).find('priority').text();
cur_priority = !isNaN(cur_priority) ? parseInt(cur_priority) : 0;
// Higher priority?
if((cur_priority >= max_priority) || (max_priority == null)) {
max_priority = cur_priority;
from_highest = cur_from;
}
}
}
logThis('Processed presence for regular user: ' + xid + ' (highest priority for: ' + (from_highest || 'none') + ')');
}
}
if(from_highest)
setDB('presence-priority', xid, from_highest);
else
removeDB('presence-priority', xid);
} catch(e) {
logThis('Error on presence processing: ' + e, 1);
}
}
// Returns the highest presence priority XID for an user
function highestPriority(xid) {
return getDB('presence-priority', xid) || '';
}
// Gets the resource from a XID which has the highest priority
function highestPriorityStanza(xid) {
var pr;
var highest = highestPriority(xid);
if(highest) pr = getDB('presence-stanza', highest);
if(!pr) pr = '<presence><type>unavailable</type></presence>';
return XMLFromString(pr);
}
// Lists presence resources for an user
function resourcesPresence(xid) {
try {
var resources_obj = {};
var resources_db = getDB('presence-resources', xid);
if(resources_db) {
resources_obj = $.evalJSON(resources_db);
}
return resources_obj;
} catch(e) {}
}
// Adds a given presence resource for an user
function addResourcePresence(xid, resource) {
try {
var resources_obj = resourcesPresence(xid);
resources_obj[resource] = 1;
setDB('presence-resources', xid, $.toJSON(resources_obj));
return resources_obj;
} catch(e) {}
return null;
}
// Removes a given presence resource for an user
function removeResourcePresence(xid, resource) {
try {
var resources_obj = resourcesPresence(xid);
delete resources_obj[resource];
setDB('presence-resources', xid, $.toJSON(resources_obj));
return resources_obj;
} catch(e) {}
return null;
}
// Makes something easy to process for the presence IA
function presenceFunnel(xid, hash) {
// Get the highest priority presence value
var xml = $(highestPriorityStanza(xid));
var type = xml.find('type').text();
var show = xml.find('show').text();
var status = xml.find('status').text();
var avatar = xml.find('avatar').text();
var checksum = xml.find('checksum').text();
var caps = xml.find('caps').text();
// Display the presence with that stored value
if(!type && !show)
presenceIA('', 'available', status, hash, xid, avatar, checksum, caps);
else
presenceIA(type, show, status, hash, xid, avatar, checksum, caps);
}
// Sends a defined presence packet
function sendPresence(to, type, show, status, checksum, limit_history, password, handle) {
// Get some stuffs
var priority = getDB('priority', 1);
if(!priority)
priority = '1';
if(!checksum)
checksum = getDB('checksum', 1);
if(show == 'available')
show = '';
if(type == 'available')
type = '';
// New presence
var presence = new JSJaCPresence();
// Avoid "null" or "none" if nothing stored
if(!checksum || (checksum == 'none'))
checksum = '';
// Presence headers
if(to)
presence.setTo(to);
if(type)
presence.setType(type);
if(show)
presence.setShow(show);
if(status)
presence.setStatus(status);
presence.setPriority(priority);
// CAPS (entity capabilities)
presence.appendNode('c', {'xmlns': NS_CAPS, 'hash': 'sha-1', 'node': 'http://jappix.org/', 'ver': myCaps()});
// Nickname
var nickname = getName();
if(nickname && !limit_history)
presence.appendNode('nick', {'xmlns': NS_NICK}, nickname);
// vcard-temp:x:update node
var x = presence.appendNode('x', {'xmlns': NS_VCARD_P});
x.appendChild(presence.buildNode('photo', {'xmlns': NS_VCARD_P}, checksum));
// MUC X data
if(limit_history || password) {
var xMUC = presence.appendNode('x', {'xmlns': NS_MUC});
// Max messages age (for MUC)
if(limit_history)
xMUC.appendChild(presence.buildNode('history', {'maxstanzas': 20, 'seconds': 86400, 'xmlns': NS_MUC}));
// Room password
if(password)
xMUC.appendChild(presence.buildNode('password', {'xmlns': NS_MUC}, password));
}
// If away, send a last activity time
if((show == 'away') || (show == 'xa')) {
/* REF: http://xmpp.org/extensions/xep-0256.html */
presence.appendNode(presence.buildNode('query', {
'xmlns': NS_LAST,
'seconds': getPresenceLast()
}));
}
// Else, set a new last activity stamp
else
PRESENCE_LAST_ACTIVITY = getTimeStamp();
// Send the presence packet
if(handle)
con.send(presence, handle);
else
con.send(presence);
if(!type)
type = 'available';
logThis('Presence sent: ' + type, 3);
}
// Performs all the actions to get the presence data
function presenceSend(checksum, autoidle) {
// We get the values of the inputs
var show = getUserShow();
var status = getUserStatus();
// Send the presence
if(!isAnonymous())
sendPresence('', '', show, status, checksum);
// We set the good icon
presenceIcon(show);
// We store our presence
if(!autoidle)
setDB('presence-show', 1, show);
// We send the presence to our active MUC
$('.page-engine-chan[data-type="groupchat"]').each(function() {
var tmp_nick = $(this).attr('data-nick');
if(!tmp_nick)
return;
var room = unescape($(this).attr('data-xid'));
var nick = unescape(tmp_nick);
// Must re-initialize?
if(RESUME)
getMUC(room, nick);
// Not disabled?
else if(!$(this).find('.message-area').attr('disabled'))
sendPresence(room + '/' + nick, '', show, status, '', true);
});
}
// Changes the presence icon
function presenceIcon(value) {
$('#my-infos .f-presence a.picker').attr('data-value', value);
}
// Sends a subscribe stanza
function sendSubscribe(to, type) {
var status = '';
// Subscribe request?
if(type == 'subscribe')
status = printf(_e("Hi, I am %s, I would like to add you as my friend."), getName());
sendPresence(to, type, '', status);
}
// Accepts the subscription from another entity
function acceptSubscribe(xid, name) {
// We update our chat
$('#' + hex_md5(xid) + ' .tools-add').hide();
// We send a subsribed presence (to confirm)
sendSubscribe(xid, 'subscribed');
// We send a subscription request (subscribe both sides)
sendSubscribe(xid, 'subscribe');
// Specify the buddy name (if any)
if(name)
sendRoster(xid, '', name)
}
// Sends automatic away presence
var AUTO_IDLE = false;
function autoIdle() {
// Not connected?
if(!isConnected())
return;
// Stop if an xa presence was set manually
var last_presence = getUserShow();
if(!AUTO_IDLE && ((last_presence == 'away') || (last_presence == 'xa')))
return;
var idle_presence;
var activity_limit;
// Can we extend to auto extended away mode (20 minutes)?
if(AUTO_IDLE && (last_presence == 'away')) {
idle_presence = 'xa';
activity_limit = 1200;
}
// We must set the user to auto-away (10 minutes)
else {
idle_presence = 'away';
activity_limit = 600;
}
// The user is really inactive and has set another presence than extended away
if(((!AUTO_IDLE && (last_presence != 'away')) || (AUTO_IDLE && (last_presence == 'away'))) && (getLastActivity() >= activity_limit)) {
// Then tell we use an auto presence
AUTO_IDLE = true;
// Get the old status message
var status = getDB('options', 'presence-status');
if(!status)
status = '';
// Change the presence input
$('#my-infos .f-presence a.picker').attr('data-value', idle_presence);
$('#presence-status').val(status);
// Then send the xa presence
presenceSend('', true);
logThis('Auto-idle presence sent: ' + idle_presence, 3);
}
}
// Restores the old presence on a document bind
function eventIdle() {
// If we were idle, restore our old presence
if(AUTO_IDLE) {
// Get the values
var show = getDB('presence-show', 1);
var status = getDB('options', 'presence-status');
// Change the presence input
$('#my-infos .f-presence a.picker').attr('data-value', show);
$('#presence-status').val(status);
$('#presence-status').placeholder();
// Then restore the old presence
presenceSend('', true);
if(!show)
show = 'available';
logThis('Presence restored: ' + show, 3);
}
// Apply some values
AUTO_IDLE = false;
LAST_ACTIVITY = getTimeStamp();
}
// Lives the auto idle functions
function liveIdle() {
// Apply the autoIdle function every minute
AUTO_IDLE = false;
$('#my-infos .f-presence').everyTime('30s', autoIdle);
// On body bind (click & key event)
$('body').on('mousedown', eventIdle)
.on('mousemove', eventIdle)
.on('keydown', eventIdle);
}
// Kills the auto idle functions
function dieIdle() {
// Remove the event detector
$('body').off('mousedown', eventIdle)
.off('mousemove', eventIdle)
.off('keydown', eventIdle);
}
// Gets the user presence show
function getUserShow() {
return $('#my-infos .f-presence a.picker').attr('data-value');
}
// Gets the user presence status
function getUserStatus() {
return $('#presence-status').val();
}
// Plugin launcher
function launchPresence() {
// Click event for user presence show
$('#my-infos .f-presence a.picker').click(function() {
// Disabled?
if($(this).hasClass('disabled'))
return false;
// Initialize some vars
var path = '#my-infos .f-presence div.bubble';
var show_id = ['xa', 'away', 'available'];
var show_lang = [_e("Not available"), _e("Away"), _e("Available")];
var show_val = getUserShow();
// Yet displayed?
var can_append = true;
if(exists(path))
can_append = false;
// Add this bubble!
showBubble(path);
if(!can_append)
return false;
// Generate the HTML code
var html = '<div class="bubble removable">';
for(i in show_id) {
// Yet in use: no need to display it!
if(show_id[i] == show_val)
continue;
html += '<a href="#" class="talk-images" data-value="' + show_id[i] + '" title="' + show_lang[i] + '"></a>';
}
html += '</div>';
// Append the HTML code
$('#my-infos .f-presence').append(html);
// Click event
$(path + ' a').click(function() {
// Update the presence show marker
$('#my-infos .f-presence a.picker').attr('data-value', $(this).attr('data-value'));
// Close the bubble
closeBubbles();
// Focus on the status input
$(document).oneTime(10, function() {
$('#presence-status').focus();
});
return false;
});
return false;
});
// Submit events for user presence status
$('#presence-status').placeholder()
.keyup(function(e) {
if(e.keyCode == 13) {
$(this).blur();
return false;
}
})
.blur(function() {
// Read the parameters
var show = getUserShow();
var status = getUserStatus();
// Read the old parameters
var old_show = getDB('presence-show', 1);
var old_status = getDB('options', 'presence-status');
// Must send the presence?
if((show != old_show) || (status != old_status)) {
// Update the local stored status
setDB('options', 'presence-status', status);
// Update the server stored status
if(status != old_status)
storeOptions();
// Send the presence
presenceSend();
}
})
// Input focus handler
.focus(function() {
closeBubbles();
});
}
/*
Jappix - An open social platform
These are the roster JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 08/08/12
*/
// Gets the roster items
function getRoster() {
var iq = new JSJaCIQ();
iq.setType('get');
iq.setQuery(NS_ROSTER);
con.send(iq, handleRoster);
}
// Handles the roster items
function handleRoster(iq) {
// Parse the roster xml
$(iq.getQuery()).find('item').each(function() {
// Get user data
var self = $(this);
var user_xid = self.attr('jid');
var user_subscription = self.attr('subscription');
// Parse roster data & display user
parseRoster($(this), 'load');
// Request user microblog (populates channel)
if(user_xid && ((user_subscription == 'both') || (user_subscription == 'to')))
requestMicroblog(user_xid, 1, null, handleRosterMicroblog);
});
// Update our avatar (if changed), and send our presence
getAvatar(getXID(), 'force', 'true', 'forget');
logThis('Roster received.');
}
// Parses the group XML and display the roster
function parseRoster(current, mode) {
// Get the values
xid = current.attr('jid');
dName = current.attr('name');
subscription = current.attr('subscription');
xidHash = hex_md5(xid);
// Create an array containing the groups
var groups = new Array();
current.find('group').each(function() {
var group_text = $(this).text();
if(group_text)
groups.push(group_text);
});
// No group?
if(!groups.length)
groups.push(_e("Unclassified"));
// If no name is defined, we get the default nick of the buddy
if(!dName)
dName = getXIDNick(xid);
displayRoster(xid, xidHash, dName, subscription, groups, mode);
}
// Updates the roster groups
function updateGroups() {
$('#buddy-list .one-group').each(function() {
// Current values
var check = $(this).find('.buddy').size();
var hidden = $(this).find('.buddy:not(.hidden-buddy:hidden)').size();
// Special case: the filtering tool
if(SEARCH_FILTERED)
hidden = $(this).find('.buddy:visible').size();
// If the group is empty
if(!check)
$(this).remove();
// If the group contains no online buddy (and is not just hidden)
if(!hidden && $(this).find('a.group').hasClass('minus'))
$(this).hide();
else
$(this).show();
});
}
// Displays a defined roster item
function displayRoster(dXID, dXIDHash, dName, dSubscription, dGroup, dMode) {
// First remove the buddy
$('#buddy-list .' + dXIDHash).remove();
// Define some things around the groups
var is_gateway = isGateway(dXID);
var gateway = '';
if(is_gateway) {
gateway = ' gateway';
dGroup = new Array(_e("Gateways"));
}
// Remove request
if(dSubscription == 'remove') {
// Flush presence
flushPresence(dXID);
presenceFunnel(dXID, dXIDHash);
// Empty social channel
$('#channel .mixed .one-update.update_' + dXIDHash).remove();
}
// Other request
else {
// Is this buddy blocked?
var privacy_class = '';
var privacy_state = statusPrivacy('block', dXID);
if(privacy_state == 'deny')
privacy_class = ' blocked';
// For each group this buddy has
for(i in dGroup) {
var cGroup = dGroup[i];
if(cGroup) {
// Process some vars
var groupHash = 'group' + hex_md5(cGroup);
var groupContent = '#buddy-list .' + groupHash;
var groupBuddies = groupContent + ' .group-buddies';
// Is this group blocked?
if((statusPrivacy('block', cGroup) == 'deny') && (privacy_state != 'allow'))
privacy_class = ' blocked';
// Group not yet displayed
if(!exists(groupContent)) {
// Define some things
var groupCont = '#buddy-list .content';
var groupToggle = groupCont + ' .' + groupHash + ' a.group';
// Create the HTML markup of the group
$(groupCont).prepend(
'<div class="' + groupHash + ' one-group" data-group="' + escape(cGroup) + '">' +
'<a href="#" class="group talk-images minus">' + cGroup.htmlEnc() + '</a>' +
'<div class="group-buddies"></div>' +
'</div>'
);
// Create the click event which will hide and show the content
$(groupToggle).click(function() {
var group = $(groupBuddies);
var group_toggle = $(groupContent + ' a.group');
// We must hide the buddies
if(group_toggle.hasClass('minus')) {
group.hide();
group_toggle.removeClass('minus').addClass('plus');
// Remove the group opened buddy-info
closeBubbles();
}
// We must show the buddies
else {
group_toggle.removeClass('plus').addClass('minus');
group.show();
}
return false;
});
}
// Initialize the HTML code
var name_code = '<p class="buddy-name">' + dName.htmlEnc() + '</p>';
var presence_code = '<p class="buddy-presence talk-images unavailable">' + _e("Unavailable") + '</p>';
var html = '<div class="hidden-buddy buddy ibubble ' + dXIDHash + gateway + privacy_class + '" data-xid="' + escape(dXID) + '">' +
'<div class="buddy-click">';
// Display avatar if not gateway
if(!is_gateway)
html += '<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>';
html += '<div class="name">';
// Special gateway code
if(is_gateway)
html += presence_code +
name_code;
else
html += name_code +
presence_code;
html += '</div></div></div>';
// Create the DOM element for this buddy
$(groupBuddies).append(html);
// Apply the hover event
applyBuddyHover(dXID, dXIDHash, dName, dSubscription, dGroup, groupHash);
}
}
// Click event on this buddy
$('#buddy-list .' + dXIDHash + ' .buddy-click').click(function() {
return checkChatCreate(dXID, 'chat');
});
// We get the user presence if necessary
if(dMode == 'presence')
presenceFunnel(dXID, dXIDHash);
// If the buddy must be shown
if(BLIST_ALL)
$('#buddy-list .' + dXIDHash).show();
}
// We update our groups
if(!SEARCH_FILTERED)
updateGroups();
else
funnelFilterBuddySearch();
}
// Applies the buddy editing input events
function applyBuddyInput(xid) {
// Initialize
var path = '#buddy-list .buddy[data-xid="' + escape(xid) + '"]';
var rename = path + ' .bm-rename input';
var group = path + ' .bm-group input';
var manage_infos = path + ' .manage-infos';
var bm_choose = manage_infos + ' div.bm-choose';
// Keyup events
$(rename).keyup(function(e) {
if(e.keyCode == 13) {
// Send the item
sendRoster(xid, '', trim($(rename).val()), thisBuddyGroups(xid));
// Remove the buddy editor
closeBubbles();
return false;
}
});
$(group).keyup(function(e) {
if(e.keyCode == 13) {
// Empty input?
if(!trim($(this).val())) {
// Send the item
sendRoster(xid, '', trim($(rename).val()), thisBuddyGroups(xid));
// Remove the buddy editor
closeBubbles();
return false;
}
// Get the values
var this_value = trim($(this).val());
var escaped_value = escape(this_value);
// Check if the group yet exists
var group_exists = false;
$(bm_choose + ' label span').each(function() {
if($(this).text() == this_value)
group_exists = true;
});
// Create a new checked checkbox
if(!group_exists)
$(bm_choose).prepend('<label><input type="checkbox" data-group="' + escaped_value + '" /><span>' + this_value.htmlEnc() + '</span></label>');
// Check the checkbox
$(bm_choose + ' input[data-group="' + escaped_value + '"]').attr('checked', true);
// Reset the value of this input
$(this).val('');
return false;
}
});
// Click events
$(manage_infos + ' p.bm-authorize a.to').click(function() {
closeBubbles();
sendSubscribe(xid, 'subscribed');
return false;
});
$(manage_infos + ' p.bm-authorize a.from').click(function() {
closeBubbles();
sendSubscribe(xid, 'subscribe');
return false;
});
$(manage_infos + ' p.bm-authorize a.unblock').click(function() {
closeBubbles();
// Update privacy settings
pushPrivacy('block', ['jid'], [xid], ['allow'], [false], [true], [true], [true], '', 'roster');
$(path).removeClass('blocked');
// Enable the "block" list
changePrivacy('block', 'active');
changePrivacy('block', 'default');
// Send an available presence
sendPresence(xid, 'available', getUserShow(), getUserStatus());
return false;
});
$(manage_infos + ' p.bm-remove a.remove').click(function() {
closeBubbles();
// First unregister if gateway
if(isGateway(xid))
unregisterGatewayRoster(xid);
// Then send roster removal query
sendRoster(xid, 'remove');
return false;
});
$(manage_infos + ' p.bm-remove a.prohibit').click(function() {
closeBubbles();
sendSubscribe(xid, 'unsubscribed');
return false;
});
$(manage_infos + ' p.bm-remove a.block').click(function() {
closeBubbles();
// Update privacy settings
pushPrivacy('block', ['jid'], [xid], ['deny'], [false], [true], [true], [true], '', 'roster');
$(path).addClass('blocked');
// Enable the "block" list
changePrivacy('block', 'active');
changePrivacy('block', 'default');
// Send an unavailable presence
sendPresence(xid, 'unavailable');
// Remove the user presence
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored presence
if((explodeThis('_', current, 0) == 'presence') && (bareXID(explodeThis('_', current, 1)) == xid))
storageDB.removeItem(current);
}
// Manage his new presence
presenceFunnel(xid, hex_md5(xid));
return false;
});
$(manage_infos + ' a.save').click(function() {
// Send the item
sendRoster(xid, '', trim($(rename).val()), thisBuddyGroups(xid));
// Remove the buddy editor
closeBubbles();
return false;
});
}
// Applies the buddy editing hover events
function applyBuddyHover(xid, hash, nick, subscription, groups, group_hash) {
// Generate the values
var bPath = '#buddy-list .' + group_hash + ' .buddy[data-xid="' + escape(xid) + '"]';
var iPath = bPath + ' .buddy-infos';
// Apply the hover event
$(bPath).hover(function() {
// Another bubble exist
if(exists('#buddy-list .buddy-infos'))
return false;
$(bPath).oneTime(200, function() {
// Another bubble exist
if(exists('#buddy-list .buddy-infos'))
return false;
// Add this bubble!
showBubble(iPath);
// Create the buddy infos DOM element
$(bPath).append(
'<div class="buddy-infos bubble removable">' +
'<div class="buddy-infos-subarrow talk-images"></div>' +
'<div class="buddy-infos-subitem">' +
'<div class="pep-infos">' +
'<p class="bi-status talk-images unavailable">' + _e("unknown") + '</p>' +
'<p class="bi-mood talk-images mood-four">' + _e("unknown") + '</p>' +
'<p class="bi-activity talk-images activity-exercising">' + _e("unknown") + '</p>' +
'<p class="bi-tune talk-images tune-note">' + _e("unknown") + '</p>' +
'<p class="bi-geoloc talk-images location-world">' + _e("unknown") + '</p>' +
'<p class="bi-view talk-images view-individual"><a href="#" class="profile">' + _e("Profile") + '</a> / <a href="#" class="channel">' + _e("Channel") + '</a> / <a href="#" class="commands">' + _e("Commands") + '</a></p>' +
'<p class="bi-edit talk-images edit-buddy"><a href="#">' + _e("Edit") + '</a></p>' +
'</div>' +
'</div>' +
'</div>'
);
// Sets the good position
buddyInfosPosition(xid, group_hash);
// Get the presence
presenceFunnel(xid, hash);
// Get the PEP infos
displayAllPEP(xid);
// Click events
$(bPath + ' .bi-view a').click(function() {
// Renitialize the buddy infos
closeBubbles();
// Profile
if($(this).is('.profile'))
openUserInfos(xid);
// Channel
else if($(this).is('.channel'))
fromInfosMicroblog(xid, hash);
// Command
else if($(this).is('.commands'))
retrieveAdHoc(xid);
return false;
});
$(bPath + ' .bi-edit a').click(function() {
buddyEdit(xid, nick, subscription, groups);
return false;
});
});
}, function() {
if(!exists(iPath + ' .manage-infos'))
closeBubbles();
$(bPath).stopTime();
});
}
// Sets the good buddy-infos position
function buddyInfosPosition(xid, group_hash) {
// Paths
var group = '#buddy-list .' + group_hash;
var buddy = group + ' .buddy[data-xid="' + escape(xid) + '"]';
var buddy_infos = buddy + ' .buddy-infos';
// Get the offset to define
var offset = 3;
if(isGateway(xid))
offset = -8;
// Process the position
var v_position = $(buddy).position().top + offset;
var h_position = $(buddy).width() - 10;
// Apply the top position
$(buddy_infos).css('top', v_position);
// Apply the left/right position
if($('html').attr('dir') == 'rtl')
$(buddy_infos).css('right', h_position);
else
$(buddy_infos).css('left', h_position);
}
// Generates an array of the current groups of a buddy
function thisBuddyGroups(xid) {
var path = '#buddy-list .buddy[data-xid="' + escape(xid) + '"] ';
var array = new Array();
// Each checked checkboxes
$(path + 'div.bm-choose input[type="checkbox"]').filter(':checked').each(function() {
array.push(unescape($(this).attr('data-group')));
});
// Entered input value (and not yet in the array)
var value = trim($(path + 'p.bm-group input').val());
if(value && !existArrayValue(array, value))
array.push(value);
return array;
}
// Adds a given contact to our roster
function addThisContact(xid, name) {
logThis('Add this contact: ' + xid + ', as ' + name, 3);
// Cut the resource of this XID
xid = bareXID(xid);
// If the form is complete
if(xid) {
// We send the subscription
sendSubscribe(xid, 'subscribe');
sendRoster(xid, '', name);
// We hide the bubble
closeBubbles();
}
}
// Gets an array of all the groups in the roster
function getAllGroups() {
var groups = new Array();
$('#buddy-list .one-group').each(function() {
var current = unescape($(this).attr('data-group'));
if((current != _e("Unclassified")) && (current != _e("Gateways")))
groups.push(current);
});
return groups.sort();
}
// Edits buddy informations
function buddyEdit(xid, nick, subscription, groups) {
logThis('Buddy edit: ' + xid, 3);
// Initialize
var path = '#buddy-list .buddy[data-xid="' + escape(xid) + '"] .';
var html = '<div class="manage-infos">';
// Get the privacy state
var privacy_state = statusPrivacy('block', xid);
var privacy_active = getDB('privacy-marker', 'available');
// Get the group privacy state
for(g in groups) {
if((statusPrivacy('block', groups[g]) == 'deny') && (privacy_state != 'allow'))
privacy_state = 'deny';
}
// The subscription with this buddy is not full
if((subscription != 'both') || ((privacy_state == 'deny') && privacy_active)) {
var authorize_links = '';
html += '<p class="bm-authorize talk-images">';
// Link to allow to see our status
if((subscription == 'to') || (subscription == 'none'))
authorize_links += '<a href="#" class="to">' + _e("Authorize") + '</a>';
// Link to ask to see his/her status
if((subscription == 'from') || (subscription == 'none')) {
if(authorize_links)
authorize_links += ' / ';
authorize_links += '<a href="#" class="from">' + _e("Ask for authorization") + '</a>';
}
// Link to unblock this buddy
if((privacy_state == 'deny') && privacy_active) {
if(authorize_links)
authorize_links += ' / ';
html += '<a href="#" class="unblock">' + _e("Unblock") + '</a>';
}
html += authorize_links + '</p>';
}
// Complete the HTML code
var remove_links = '';
html += '<p class="bm-remove talk-images">';
remove_links = '<a href="#" class="remove">' + _e("Remove") + '</a>';
// This buddy is allowed to see our presence, we can show a "prohibit" link
if((subscription == 'both') || (subscription == 'from'))
remove_links += ' / <a href="#" class="prohibit">' + _e("Prohibit") + '</a>';
// Complete the HTML code
if((privacy_state != 'deny') && privacy_active) {
if(remove_links)
remove_links += ' / ';
remove_links += '<a href="#" class="block">' + _e("Block") + '</a>';
}
// Complete the HTML code
html += remove_links +
'</p>' +
'<p class="bm-rename talk-images"><label>' + _e("Rename") + '</label> <input type="text" value="' + encodeQuotes(nick) + '" /></p>';
// Only show group tool if not a gateway
if(!isGateway(xid))
html += '<p class="bm-group talk-images"><label>' + _e("Groups") + '</label> <input type="text" /></p>' +
'<div class="bm-choose">' +
'<div></div>' +
'</div>';
// Close the DOM element
html += '<a href="#" class="save">' + _e("Save") + '</a>' +
'</div>';
// We update the DOM elements
$(path + 'pep-infos').replaceWith(html);
// Gets all the existing groups
var all_groups = getAllGroups();
var all_groups_dom = '';
for(a in all_groups) {
// Current group
var all_groups_current = all_groups[a];
// Is the current group checked?
var checked = '';
if(existArrayValue(groups, all_groups_current))
checked = ' checked="true"';
// Add the current group HTML
all_groups_dom += '<label><input type="checkbox" data-group="' + escape(all_groups_current) + '"' + checked + ' /><span>' + all_groups_current.htmlEnc() + '</span></label>';
}
// Prepend this in the DOM
var bm_choose = path + 'manage-infos div.bm-choose';
$(bm_choose).prepend(all_groups_dom);
// Apply the editing input events
applyBuddyInput(xid);
}
// Unregisters from a given gateway
function unregisterGatewayRoster(xid) {
var iq = new JSJaCIQ();
iq.setType('set');
iq.setTo(xid);
var query = iq.setQuery(NS_REGISTER);
query.appendChild(iq.buildNode('remove', {'xmlns': NS_REGISTER}));
con.send(iq);
}
// Updates the roster items
function sendRoster(xid, subscription, name, group) {
// We send the new buddy name
var iq = new JSJaCIQ();
iq.setType('set');
var iqQuery = iq.setQuery(NS_ROSTER);
var item = iqQuery.appendChild(iq.buildNode('item', {'xmlns': NS_ROSTER, 'jid': xid}));
// Any subscription?
if(subscription)
item.setAttribute('subscription', subscription);
// Any name?
if(name)
item.setAttribute('name', name);
// Any group?
if(group && group.length) {
for(i in group)
item.appendChild(iq.buildNode('group', {'xmlns': NS_ROSTER}, group[i]));
}
con.send(iq);
logThis('Roster item sent: ' + xid, 3);
}
// Adapts the roster height, depending of the window size
function adaptRoster() {
// Process the new height
var new_height = $('#left-content').height() - $('#my-infos').height() - 97;
// New height too small
if(new_height < 211)
new_height = 211;
// Apply the new height
$('#buddy-list .content').css('height', new_height);
}
// Gets all the buddies in our roster
function getAllBuddies() {
var buddies = new Array();
$('#buddy-list .buddy').each(function() {
var xid = unescape($(this).attr('data-xid'));
if(xid)
buddies.push(xid);
});
return buddies;
}
// Gets the user gateways
function getGateways() {
// New array
var gateways = new Array();
var buddies = getAllBuddies();
// Get the gateways
for(c in buddies) {
if(isGateway(buddies[c]))
gateways.push(buddies[c]);
}
return gateways;
}
// Define a global var for buddy list all buddies displayed
var BLIST_ALL = false;
// Plugin launcher
function launchRoster() {
// Filtering tool
var iFilter = $('#buddy-list .filter input');
var aFilter = $('#buddy-list .filter a');
iFilter.placeholder()
.blur(function() {
// Nothing is entered, put the placeholder instead
if(!trim($(this).val()))
aFilter.hide();
else
aFilter.show();
})
.keyup(function(e) {
funnelFilterBuddySearch(e.keyCode);
});
aFilter.click(function() {
// Reset the input
$(this).hide();
iFilter.val('');
iFilter.placeholder();
// Security: show all the groups, empty or not
$('#buddy-list .one-group').show();
// Reset the filtering tool
resetFilterBuddySearch();
return false;
});
// When the user click on the add button, show the contact adding tool
$('#buddy-list .foot .add').click(function() {
// Yet displayed?
if(exists('#buddy-conf-add'))
return closeBubbles();
// Add the bubble
showBubble('#buddy-conf-add');
// Append the content
$('#buddy-list .buddy-list-add').append(
'<div id="buddy-conf-add" class="buddy-conf-item bubble removable">' +
'<div class="buddy-conf-subarrow talk-images"></div>' +
'<div class="buddy-conf-subitem">' +
'<p class="buddy-conf-p">' + _e("Add a friend") + '</p>' +
'<label><span>' + _e("Address") + '</span><input type="text" class="buddy-conf-input add-contact-jid" required="" /></label>' +
'<label><span>' + _e("Name") + '</span><input type="text" class="buddy-conf-input add-contact-name" /></label>' +
'<label>' +
'<span>' + _e("Gateway") + '</span>' +
'<select class="buddy-conf-select add-contact-gateway">' +
'<option value="none" selected="">' + _e("None") + '</option>' +
'</select>' +
'</label>' +
'<span class="add-contact-name-get">' + _e("Getting the name...") + '</span>' +
'<p class="buddy-conf-text">' +
'<a href="#" class="buddy-conf-add-search">' + _e("Search a friend") + '</a>' +
'</p>' +
'</div>' +
'</div>'
);
// Add the gateways
var gateways = getGateways();
// Any gateway?
if(gateways.length) {
// Append the gateways
for(i in gateways)
$('.add-contact-gateway').append('<option value="' + escape(gateways[i]) + '">' + gateways[i].htmlEnc() + '</option>');
// Show the gateway selector
$('.add-contact-gateway').parent().show();
}
// No gateway?
else
$('.add-contact-gateway').parent().hide();
// Blur event on the add contact input
$('.add-contact-jid').blur(function() {
// Read the value
var value = trim($(this).val());
// Try to catch the buddy name
if(value && !trim($('.add-contact-name').val()) && ($('.add-contact-gateway').val() == 'none')) {
// User XID
var xid = generateXID(value, 'chat');
// Notice for the user
$('.add-contact-name-get').attr('data-for', escape(xid)).show();
// Request the user vCard
getAddUserName(xid);
}
});
// When a key is pressed...
$('#buddy-conf-add input, #buddy-conf-add select').keyup(function(e) {
// Enter : continue
if(e.keyCode == 13) {
// Get the values
var xid = trim($('.add-contact-jid').val());
var name = trim($('.add-contact-name').val());
var gateway = unescape($('.add-contact-gateway').val());
// Generate the XID to add
if((gateway != 'none') && xid)
xid = xid.replace(/@/g, '%') + '@' + gateway;
else
xid = generateXID(xid, 'chat');
// Submit the form
if(xid && getXIDNick(xid) && (xid != getXID()))
addThisContact(xid, name);
else
$(document).oneTime(10, function() {
$('.add-contact-jid').addClass('please-complete').focus();
});
return false;
}
// Escape : quit
if(e.keyCode == 27)
closeBubbles();
});
// Click event on search link
$('.buddy-conf-add-search').click(function() {
closeBubbles();
return openDirectory();
});
// Focus on the input
$(document).oneTime(10, function() {
$('.add-contact-jid').focus();
});
return false;
});
// When the user click on the join button, show the chat joining tool
$('#buddy-list .foot .join').click(function() {
// Yet displayed?
if(exists('#buddy-conf-join'))
return closeBubbles();
// Add the bubble
showBubble('#buddy-conf-join');
// Append the content
$('#buddy-list .buddy-list-join').append(
'<div id="buddy-conf-join" class="buddy-conf-item bubble removable">' +
'<div class="buddy-conf-subarrow talk-images"></div>' +
'<div class="buddy-conf-subitem search">' +
'<p class="buddy-conf-p" style="margin-bottom: 0;">' + _e("Join a chat") + '</p>' +
'<input type="text" class="buddy-conf-input join-jid" required="" />' +
'<select class="buddy-conf-select buddy-conf-join-select join-type">' +
'<option value="chat" selected="">' + _e("Chat") + '</option>' +
'<option value="groupchat">' + _e("Groupchat") + '</option>' +
'</select>' +
'</div>' +
'</div>'
);
// Input vars
var destination = '#buddy-conf-join .search';
var dHovered = destination + ' ul li.hovered:first';
// When a key is pressed...
$('#buddy-conf-join input, #buddy-conf-join select').keyup(function(e) {
// Enter: continue
if(e.keyCode == 13) {
// Select something from the search
if(exists(dHovered))
addBuddySearch(destination, $(dHovered).attr('data-xid'));
// Join something
else {
var xid = trim($('.join-jid').val());
var type = $('.buddy-conf-join-select').val();
if(xid && type) {
// Generate a correct XID
xid = generateXID(xid, type);
// Not me
if(xid != getXID()) {
// Update some things
$('.join-jid').removeClass('please-complete');
closeBubbles();
// Create a new chat
checkChatCreate(xid, type);
}
else
$('.join-jid').addClass('please-complete');
}
else
$('.join-jid').addClass('please-complete');
}
return false;
}
// Escape: quit
else if(e.keyCode == 27)
closeBubbles();
// Buddy search?
else if($('.buddy-conf-join-select').val() == 'chat') {
// New buddy search
if((e.keyCode != 40) && (e.keyCode != 38))
createBuddySearch(destination);
// Navigating with keyboard in the results
arrowsBuddySearch(e, destination);
}
});
// Buddy search lost focus
$('#buddy-conf-join input').blur(function() {
if(!$(destination + ' ul').attr('mouse-hover'))
resetBuddySearch(destination);
});
// Re-focus on the text input
$('#buddy-conf-join select').change(function() {
$(document).oneTime(10, function() {
$('#buddy-conf-join input').focus();
});
});
// We focus on the input
$(document).oneTime(10, function() {
$('#buddy-conf-join .join-jid').focus();
});
return false;
});
// When the user click on the groupchat button, show the groupchat menu
$('#buddy-list .foot .groupchat').click(function() {
// Yet displayed?
if(exists('#buddy-conf-groupchat'))
return closeBubbles();
// Add the bubble
showBubble('#buddy-conf-groupchat');
// Append the content
$('#buddy-list .buddy-list-groupchat').append(
'<div id="buddy-conf-groupchat" class="buddy-conf-item bubble removable">' +
'<div class="buddy-conf-subarrow talk-images"></div>' +
'<div class="buddy-conf-subitem">' +
'<p class="buddy-conf-p">' + _e("Your groupchats") + '</p>' +
'<select name="groupchat-join" class="buddy-conf-select buddy-conf-groupchat-select"></select>' +
'<p class="buddy-conf-text">' +
'- <a href="#" class="buddy-conf-groupchat-edit">' + _e("Manage your favorite groupchats") + '</a>' +
'</p>' +
'</div>' +
'</div>'
);
// When the user wants to edit his groupchat favorites
$('.buddy-conf-groupchat-edit').click(function() {
openFavorites();
closeBubbles();
return false;
});
// Change event
$('.buddy-conf-groupchat-select').change(function() {
var groupchat = trim($(this).val());
if(groupchat != 'none') {
// We hide the bubble
closeBubbles();
// Create the chat
checkChatCreate(groupchat, 'groupchat');
// We reset the select value
$(this).val('none');
}
});
// Load the favorites
loadFavorites();
return false;
});
// When the user click on the more button, show the more menu
$('#buddy-list .foot .more').click(function() {
// Yet displayed?
if(exists('#buddy-conf-more'))
return closeBubbles();
// Add the bubble
showBubble('#buddy-conf-more');
// Append the content
$('#buddy-list .buddy-list-more').append(
'<div id="buddy-conf-more" class="buddy-conf-item bubble removable">' +
'<div class="buddy-conf-subarrow talk-images"></div>' +
'<div class="buddy-conf-subitem">' +
'<p class="buddy-conf-p">' + _e("More stuff") + '</p>' +
'<p class="buddy-conf-text">' +
'- <a href="#" class="buddy-conf-more-display-unavailable">' + _e("Show all friends") + '</a>' +
'<a href="#" class="buddy-conf-more-display-available">' + _e("Only show connected friends") + '</a>' +
'</p>' +
'<p class="buddy-conf-text privacy-hidable">' +
'- <a href="#" class="buddy-conf-more-privacy">' + _e("Privacy") + '</a>' +
'</p>' +
'<p class="buddy-conf-text">' +
'- <a href="#" class="buddy-conf-more-service-disco">' + _e("Service discovery") + '</a>' +
'</p>' +
'<p class="buddy-conf-text commands-hidable"">' +
'- <a href="#" class="buddy-conf-more-commands">' + _e("Commands") + '</a>' +
'</p>' +
'</div>' +
'</div>'
);
// Close bubble when link clicked
$('#buddy-conf-more a').click(function() {
closeBubbles();
});
// When the user wants to display all his buddies
$('.buddy-conf-more-display-unavailable').click(function() {
showAllBuddies('roster');
return false;
});
// When the user wants to display only online buddies
$('.buddy-conf-more-display-available').click(function() {
showOnlineBuddies('roster');
return false;
});
// When the user click on the privacy link
$('.buddy-conf-more-privacy').click(openPrivacy);
// When the user click on the service discovery link
$('.buddy-conf-more-service-disco').click(openDiscovery);
// When the user click on the command link
$('.buddy-conf-more-commands').click(function() {
serverAdHoc(con.domain);
return false;
});
// Manage the displayed links
if(BLIST_ALL) {
$('.buddy-conf-more-display-unavailable').hide();
$('.buddy-conf-more-display-available').show();
}
if(enabledCommands())
$('.buddy-conf-more-commands').parent().show();
if(getDB('privacy-marker', 'available'))
$('.buddy-conf-more-privacy').parent().show();
return false;
});
// When the user scrolls the buddy list
$('#buddy-list .content').scroll(function() {
// Close the opened buddy infos bubble
closeBubbles();
});
}
// Window resize event handler
$(window).resize(adaptRoster);
/*
Jappix - An open social platform
These are the storage JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 14/05/13
*/
// Gets the storage items of the user
function getStorage(type) {
/* REF: http://xmpp.org/extensions/xep-0049.html */
var iq = new JSJaCIQ();
iq.setType('get');
var iqQuery = iq.setQuery(NS_PRIVATE);
iqQuery.appendChild(iq.buildNode('storage', {'xmlns': type}));
con.send(iq, handleStorage);
}
// Handles the storage items
function handleStorage(iq) {
var handleXML = iq.getQuery();
var handleFrom = fullXID(getStanzaFrom(iq));
// Define some vars
var options = $(handleXML).find('storage[xmlns="' + NS_OPTIONS + '"]');
var inbox = $(handleXML).find('storage[xmlns="' + NS_INBOX + '"]');
var bookmarks = $(handleXML).find('storage[xmlns="' + NS_BOOKMARKS + '"]');
var rosternotes = $(handleXML).find('storage[xmlns="' + NS_ROSTERNOTES + '"]');
// No options and node not yet configured
if(options.size() && !options.find('option').size() && (iq.getType() != 'error'))
openWelcome();
// Parse the options xml
options.find('option').each(function() {
// We retrieve the informations
var type = $(this).attr('type');
var value = $(this).text();
// We display the storage
setDB('options', type, value);
// If this is the buddy list show status
if((type == 'roster-showall') && (value == '1'))
showAllBuddies('storage');
});
// Parse the inbox xml
inbox.find('message').each(function() {
storeInboxMessage(
$(this).attr('from'),
$(this).attr('subject'),
$(this).text(),
$(this).attr('status'),
$(this).attr('id'),
$(this).attr('date'),
[
$(this).attr('file_title'),
$(this).attr('file_href'),
$(this).attr('file_type'),
$(this).attr('file_length')
]
);
});
// Parse the bookmarks xml
bookmarks.find('conference').each(function() {
// We retrieve the informations
var xid = $(this).attr('jid');
var name = $(this).attr('name');
var autojoin = $(this).attr('autojoin');
var password = $(this).find('password').text();
var nick = $(this).find('nick').text();
// Filter autojoin (compatibility)
autojoin = ((autojoin == 'true') || (autojoin == '1')) ? 'true' : 'false';
// We display the storage
displayFavorites(xid, name, nick, autojoin, password);
// Join the chat if autojoin is enabled
if(autojoin == 'true')
checkChatCreate(xid, 'groupchat', nick, password, name);
});
// Parse the roster notes xml
rosternotes.find('note').each(function() {
setDB('rosternotes', $(this).attr('jid'), $(this).text());
});
// Options received
if(options.size()) {
logThis('Options received.');
// Now, get the inbox
getStorage(NS_INBOX);
// Geolocate the user
geolocate();
$('.options-hidable').show();
}
// Inbox received
else if(inbox.size()) {
logThis('Inbox received.');
// Send the first presence!
firstPresence(getDB('checksum', 1));
// Check we have new messages (play a sound if any unread messages)
if(checkInboxMessages())
soundPlay(2);
$('.inbox-hidable').show();
}
// Bookmarks received
else if(bookmarks.size()) {
// Join the groupchats the admin defined (if any)
joinConfGroupchats();
logThis('Bookmarks received.');
}
// Roster notes received (for logger)
else if(rosternotes.size())
logThis('Roster notes received.');
}
/*
Jappix - An open social platform
These are the common JS script for Jappix
-------------------------------------------------
License: dual-licensed under AGPL and MPLv2
Authors: Valérian Saliou, olivierm, regilero, Maranda
Last revision: 24/09/12
*/
// Checks if an element exists in the DOM
function exists(selector) {
if(jQuery(selector).size() > 0)
return true;
else
return false;
}
// Checks if Jappix is connected
function isConnected() {
if((typeof con != 'undefined') && con && con.connected())
return true;
return false;
}
// Checks if Jappix has focus
function isFocused() {
try {
if(document.hasFocus())
return true;
return false;
}
catch(e) {
return true;
}
}
// Generates the good XID
function generateXID(xid, type) {
// XID needs to be transformed
// .. and made lowercase (uncertain though this is the right place...)
xid = xid.toLowerCase();
if(xid && (xid.indexOf('@') == -1)) {
// Groupchat
if(type == 'groupchat')
return xid + '@' + HOST_MUC;
// One-to-one chat
if(xid.indexOf('.') == -1)
return xid + '@' + HOST_MAIN;
// It might be a gateway?
return xid;
}
// Nothing special (yet bare XID)
return xid;
}
// Gets the asked translated string
function _e(string) {
return string;
}
// Replaces '%s' to a given value for a translated string
function printf(string, value) {
return string.replace('%s', value);
}
// Returns the string after the last given char
function strAfterLast(given_char, str) {
if(!given_char || !str)
return '';
var char_index = str.lastIndexOf(given_char);
var str_return = str;
if(char_index >= 0)
str_return = str.substr(char_index + 1);
return str_return;
}
// Properly explodes a string with a given character
function explodeThis(toEx, toStr, i) {
// Get the index of our char to explode
var index = toStr.indexOf(toEx);
// We split if necessary the string
if(index != -1) {
if(i == 0)
toStr = toStr.substr(0, index);
else
toStr = toStr.substr(index + 1);
}
// We return the value
return toStr;
}
// Cuts the resource of a XID
function cutResource(aXID) {
return explodeThis('/', aXID, 0);
}
// Gets the resource of a XID
function thisResource(aXID) {
// Any resource?
if(aXID.indexOf('/') != -1)
return explodeThis('/', aXID, 1);
// No resource
return '';
}
// nodepreps an XMPP node
function nodeprep(node) {
// Spec: http://tools.ietf.org/html/rfc6122#appendix-A
if(!node)
return node;
// Remove prohibited chars
var prohibited_chars = ['"', '&', '\'', '/', ':', '<', '>', '@'];
for(j in prohibited_chars)
node = node.replace(prohibited_chars[j], '');
// Lower case
node = node.toLowerCase();
return node;
}
// Encodes quotes in a string
function encodeQuotes(str) {
return (str + '').htmlEnc();
}
// Gets the bare XID from a XID
function bareXID(xid) {
// Cut the resource
xid = cutResource(xid);
// Launch nodeprep
if(xid.indexOf('@') != -1)
xid = nodeprep(getXIDNick(xid)) + '@' + getXIDHost(xid);
return xid;
}
// Gets the full XID from a XID
function fullXID(xid) {
// Normalizes the XID
var full = bareXID(xid);
var resource = thisResource(xid);
// Any resource?
if(resource)
full += '/' + resource;
return full;
}
// Gets the nick from a XID
function getXIDNick(aXID) {
// Gateway nick?
if(aXID.match(/\\40/))
return explodeThis('\\40', aXID, 0);
return explodeThis('@', aXID, 0);
}
// Gets the host from a XID
function getXIDHost(aXID) {
return explodeThis('@', aXID, 1);
}
// Checks if we are in developer mode
function isDeveloper() {
return (DEVELOPER == 'on');
}
// Checks if we are RTL (Right-To-Left)
function isRTL() {
return (_e("default:LTR") == 'default:RTL');
}
// Checks if anonymous mode is allowed
function allowedAnonymous() {
return (ANONYMOUS == 'on');
}
// Checks if host is locked
function lockHost() {
return (LOCK_HOST == 'on');
}
// Gets the full XID of the user
function getXID() {
// Return the XID of the user
if(con.username && con.domain)
return con.username + '@' + con.domain;
return '';
}
// Generates the colors for a given user XID
function generateColor(xid) {
var colors = new Array(
'ac0000',
'a66200',
'007703',
'00705f',
'00236b',
'4e005c'
);
var number = 0;
for(var i = 0; i < xid.length; i++)
number += xid.charCodeAt(i);
var color = '#' + colors[number % (colors.length)];
return color;
}
// Checks if the XID is a gateway
function isGateway(xid) {
if(xid.indexOf('@') != -1)
return false;
return true;
}
// Gets the from attribute of a stanza (overrides some servers like Prosody missing from attributes)
function getStanzaFrom(stanza) {
var from = stanza.getFrom();
// No from, we assume this is our XID
if(!from)
from = getXID();
return from;
}
// Logs a given data in the console
function logThis(data, level) {
// Console not available
if(!isDeveloper() || (typeof(console) == 'undefined'))
return false;
// Switch the log level
switch(level) {
// Debug
case 0:
console.debug(data);
break;
// Error
case 1:
console.error(data);
break;
// Warning
case 2:
console.warn(data);
break;
// Information
case 3:
console.info(data);
break;
// Default log level
default:
console.log(data);
break;
}
return true;
}
// Gets the current Jappix app. location
function getJappixLocation() {
var url = window.location.href;
// If the URL has variables, remove them
if(url.indexOf('?') != -1)
url = url.split('?')[0];
if(url.indexOf('#') != -1)
url = url.split('#')[0];
// No "/" at the end
if(!url.match(/(.+)\/$/))
url += '/';
return url;
}
// Removes spaces at the beginning & the end of a string
function trim(str) {
return str.replace(/^\s+/g,'').replace(/\s+$/g,'');
}
// Adds a zero to a date when needed
function padZero(i) {
// Negative number (without first 0)
if(i > -10 && i < 0)
return '-0' + (i * -1);
// Positive number (without first 0)
if(i < 10 && i >= 0)
return '0' + i;
// All is okay
return i;
}
// Escapes a string for a regex usage
function escapeRegex(query) {
return query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
// Returns a random array value
function randomArrayValue(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
// Returns whether the browser is mobile or not
function isMobile() {
try {
return /Android|iPhone|iPod|iPad|Windows Phone|BlackBerry|Bada|Maemo|Meego|webOS/i.test(navigator.userAgent);
} catch(e) {
return false;
}
}
/*
Jappix - An open social platform
These are the utilities JS script for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, olivierm
Last revision: 24/06/11
*/
// Checks if a function exists
function functionExists(func) {
if(typeof func == 'function')
return true;
return false;
}
// Returns whether using HTTPS or not
function isHTTPS() {
if(window.location.href && (window.location.href).match(/^https/i))
return true;
return false;
}
// Generates the good storage URL
function generateURL(url) {
// HTTPS not allowed
if((HTTPS_STORAGE != 'on') && url.match(/^https(.+)/))
url = 'http' + RegExp.$1;
return url;
}
// Disables an input if needed
function disableInput(value, condition) {
if(value == condition)
return ' disabled=""';
return '';
}
// Cuts a string
function cut(string, limit) {
return string.substr(0, limit);
}
// Truncates a string
function truncate(string, limit) {
// Must truncate the string
if(string.length > limit)
string = string.substr(0, limit) + '...';
return string;
}
// Removes the new lines
function noLines(string) {
return string.replace(/\n/g, ' ');
}
// Encodes a string for onclick attribute
function encodeOnclick(str) {
return (encodeQuotes(str)).replace(/'/g, '\\$&');
}
// Checks whether the passed parameter is a number or not
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
// Checks if we are in the anonymous mode
function isAnonymous() {
if(allowedAnonymous() && LINK_VARS['r'])
return true;
return false;
}
// Checks if this is a private chat user
function isPrivate(xid) {
if(exists('[data-xid="' + escape(xid) + '"][data-type="groupchat"]'))
return true;
return false;
}
// Checks if the user browser is obsolete
function isObsolete() {
// Get browser name & version
var browser_name = BrowserDetect.browser;
var browser_version = BrowserDetect.version;
// No DOM storage
if(!hasDB() || !hasPersistent())
return true;
// Obsolete IE
if((browser_name == 'Explorer') && (browser_version < 8))
return true;
// Obsolete Chrome
if((browser_name == 'Chrome') && (browser_version < 7))
return true;
// Obsolete Safari
if((browser_name == 'Safari') && (browser_version < 4))
return true;
// Obsolete Firefox
if((browser_name == 'Firefox') && (browser_version < 3.5))
return true;
// Obsolete Opera
if((browser_name == 'Opera') && (browser_version < 9))
return true;
return false;
}
// Gets a MUC user XID
function getMUCUserXID(room, nick) {
return $('div.chat[data-xid="' + escape(room) + '"] div[data-nick="' + escape(nick) + '"]').attr('data-xid');
}
// Gets a MUC user read XID
function getMUCUserRealXID(room, nick) {
return $('div.chat[data-xid="' + escape(room) + '"] div[data-nick="' + escape(nick) + '"]').attr('data-realxid');
}
// Gets the server of the user
function getServer() {
// Return the domain of the user
return con.domain;
}
// Gets the password of the user
function getPassword() {
// Return the password of the user
return con.pass;
}
// Quotes the nick of an user
function quoteMyNick(hash, nick) {
$(document).oneTime(10, function() {
$('#page-engine #' + hash + ' .message-area').val(nick + ', ').focus();
});
}
// Converts a XML document to a string
function xmlToString(xmlData) {
try {
// For Mozilla, Firefox, Opera, etc.
if(window.XMLSerializer)
return (new XMLSerializer()).serializeToString(xmlData);
// For Internet Explorer
if(window.ActiveXObject)
return xmlData.xml;
return null;
}
catch(e) {
return null;
}
}
// Converts a string to a XML document
function XMLFromString(sXML) {
try {
// No data?
if(!sXML)
return '';
// Add the XML tag
if(!sXML.match(/^<\?xml/i))
sXML = '<?xml version="1.0"?>' + sXML;
// Parse it!
if(window.DOMParser)
return (new DOMParser()).parseFromString(sXML, 'text/xml');
if(window.ActiveXObject) {
var oXML = new ActiveXObject('Microsoft.XMLDOM');
oXML.loadXML(sXML);
return oXML;
}
}
catch(e) {
return '';
}
}
// Return the file category
function fileCategory(ext) {
var cat;
switch(ext) {
// Images
case 'jpg':
case 'jpeg':
case 'png':
case 'bmp':
case 'gif':
case 'tif':
case 'svg':
case 'ico':
case 'psp':
case 'psd':
case 'psb':
case 'xcf':
cat = 'image';
break;
// Videos
case 'ogv':
case 'ogg':
case 'mkv':
case 'avi':
case 'mov':
case 'mp4':
case 'm4v':
case 'wmv':
case 'asf':
case 'mpg':
case 'mpeg':
case 'ogm':
case 'rmvb':
case 'rmv':
case 'qt':
case 'flv':
case 'ram':
case '3gp':
case 'avc':
cat = 'video';
break;
// Sounds
case 'oga':
case 'mka':
case 'flac':
case 'mp3':
case 'wav':
case 'm4a':
case 'wma':
case 'rmab':
case 'rma':
case 'bwf':
case 'aiff':
case 'caf':
case 'cda':
case 'atrac':
case 'vqf':
case 'au':
case 'aac':
case 'm3u':
case 'mid':
case 'mp2':
case 'snd':
case 'voc':
cat = 'audio';
break;
// Documents
case 'pdf':
case 'odt':
case 'ott':
case 'sxw':
case 'stw':
case 'ots':
case 'sxc':
case 'stc':
case 'sxi':
case 'sti':
case 'pot':
case 'odp':
case 'ods':
case 'doc':
case 'docx':
case 'docm':
case 'xls':
case 'xlsx':
case 'xlsm':
case 'xlt':
case 'ppt':
case 'pptx':
case 'pptm':
case 'pps':
case 'odg':
case 'otp':
case 'sxd':
case 'std':
case 'std':
case 'rtf':
case 'txt':
case 'htm':
case 'html':
case 'shtml':
case 'dhtml':
case 'mshtml':
cat = 'document';
break;
// Packages
case 'tgz':
case 'gz':
case 'tar':
case 'ar':
case 'cbz':
case 'jar':
case 'tar.7z':
case 'tar.bz2':
case 'tar.gz':
case 'tar.lzma':
case 'tar.xz':
case 'zip':
case 'xz':
case 'rar':
case 'bz':
case 'deb':
case 'rpm':
case '7z':
case 'ace':
case 'cab':
case 'arj':
case 'msi':
cat = 'package';
break;
// Others
default:
cat = 'other';
break;
}
return cat;
}
// Registers Jappix as the default XMPP links handler
function xmppLinksHandler() {
try {
navigator.registerProtocolHandler('xmpp', JAPPIX_LOCATION + '?x=%s', SERVICE_NAME);
return true;
}
catch(e) {
return false;
}
}
// Checks if a value exists in an array
function existArrayValue(array, value) {
try {
// Loop in the array
for(i in array) {
if(array[i] == value)
return true;
}
return false;
}
catch(e) {
return false;
}
}
// Removes a value from an array
function removeArrayValue(array, value) {
for(i in array) {
// It matches, remove it!
if(array[i] == value) {
array.splice(i, 1);
return true;
}
}
return false;
}
// Converts a string to an array
function stringToArray(string) {
var array = [];
// Any string to convert?
if(string) {
// More than one item
if(string.match(/,/gi)) {
var string_split = string.split(',');
for(i in string_split) {
if(string_split[i])
array.push(string_split[i]);
else
array.push('');
}
}
// Only one item
else
array.push(string);
}
return array;
}
// Get the index of an array value
function indexArrayValue(array, value) {
// Nothing?
if(!array || !array.length)
return 0;
// Read the index of the value
var index = 0;
for(var i = 0; i < array.length; i++) {
if(array[i] == value) {
index = i;
break;
}
}
return index;
}
// Capitalizes the first letter of a string
function capitaliseFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
/*
Jappix - An open social platform
These are the date related JS scripts for Jappix
-------------------------------------------------
License: dual-licensed under AGPL and MPLv2
Author: Valérian Saliou
Last revision: 17/08/11
*/
// Gets a stamp from a date
function extractStamp(date) {
return Math.round(date.getTime() / 1000);
}
// Gets the time from a date
function extractTime(date) {
return date.toLocaleTimeString();
}
// Gets the actual date stamp
function getTimeStamp() {
return extractStamp(new Date());
}
// Gets the last user activity in seconds
var LAST_ACTIVITY = 0;
function getLastActivity() {
// Last activity not yet initialized?
if(LAST_ACTIVITY == 0)
return 0;
return getTimeStamp() - LAST_ACTIVITY;
}
// Gets the last user available presence in seconds
var PRESENCE_LAST_ACTIVITY = 0;
function getPresenceLast() {
// Last presence stamp not yet initialized?
if(PRESENCE_LAST_ACTIVITY == 0)
return 0;
return getTimeStamp() - PRESENCE_LAST_ACTIVITY;
}
// Generates the time for XMPP
function getXMPPTime(location) {
/* FROM : http://trac.jwchat.org/jsjac/browser/branches/jsjac_1.0/jsextras.js?rev=221 */
// Initialize
var jInit = new Date();
var year, month, day, hours, minutes, seconds;
// Gets the UTC date
if(location == 'utc') {
year = jInit.getUTCFullYear();
month = jInit.getUTCMonth();
day = jInit.getUTCDate();
hours = jInit.getUTCHours();
minutes = jInit.getUTCMinutes();
seconds = jInit.getUTCSeconds();
}
// Gets the local date
else {
year = jInit.getFullYear();
month = jInit.getMonth();
day = jInit.getDate();
hours = jInit.getHours();
minutes = jInit.getMinutes();
seconds = jInit.getSeconds();
}
// Generates the date string
var jDate = year + '-';
jDate += padZero(month + 1) + '-';
jDate += padZero(day) + 'T';
jDate += padZero(hours) + ':';
jDate += padZero(minutes) + ':';
jDate += padZero(seconds) + 'Z';
// Returns the date string
return jDate;
}
// Generates then human time
function getCompleteTime() {
var init = new Date();
var time = padZero(init.getHours()) + ':';
time += padZero(init.getMinutes()) + ':';
time += padZero(init.getSeconds());
return time;
}
// Gets the TZO of a date
function getDateTZO() {
// Get the date
var date = new Date();
var offset = date.getTimezoneOffset();
// Default vars
var sign = '';
var hours = 0;
var minutes = 0;
// Process a neutral offset
if(offset < 0) {
offset = offset * -1;
sign = '+';
}
// Get the values
var n_date = new Date(offset * 60 * 1000);
hours = n_date.getHours() - 1;
minutes = n_date.getMinutes();
// Process the TZO
tzo = sign + padZero(hours) + ':' + padZero(minutes);
// Return the processed value
return tzo;
}
// Parses a XMPP date (yyyy-mm-dd, hh-mm-ss) into an human-readable one
function parseDate(to_parse) {
var date = Date.jab2date(to_parse);
var parsed = date.toLocaleDateString() + ' (' + date.toLocaleTimeString() + ')';
return parsed;
}
// Parses a XMPP date (yyyy-mm-dd) into an human-readable one
function parseDay(to_parse) {
var date = Date.jab2date(to_parse);
var parsed = date.toLocaleDateString();
return parsed;
}
// Parses a XMPP date (hh-mm-ss) into an human-readable one
function parseTime(to_parse) {
var date = Date.jab2date(to_parse);
var parsed = date.toLocaleTimeString();
return parsed;
}
// Parses a XMPP date stamp into a relative one
function relativeDate(to_parse) {
// Get the current date
var current_date = Date.jab2date(getXMPPTime('utc'));
var current_day = current_date.getDate();
var current_stamp = current_date.getTime();
// Parse the given date
var old_date = Date.jab2date(to_parse);
var old_day = old_date.getDate();
var old_stamp = old_date.getTime();
var old_time = old_date.toLocaleTimeString();
// Get the day number between the two dates
var days = Math.round((current_stamp - old_stamp) / 86400000);
// Invalid date?
if(isNaN(old_stamp) || isNaN(days))
return getCompleteTime();
// Is it today?
if(current_day == old_day)
return old_time;
// It is yesterday?
if(days <= 1)
return _e("Yesterday") + ' - ' + old_time;
// Is it less than a week ago?
if(days <= 7)
return printf(_e("%s days ago"), days) + ' - ' + old_time;
// Another longer period
return old_date.toLocaleDateString() + ' - ' + old_time;
}
// Reads a message delay
function readMessageDelay(node) {
// Initialize
var delay, d_delay;
// Read the delay
d_delay = jQuery(node).find('delay[xmlns="' + NS_URN_DELAY + '"]:first').attr('stamp');
// New delay (valid XEP)
if(d_delay)
delay = d_delay;
// Old delay (obsolete XEP!)
else {
// Try to read the old-school delay
var x_delay = jQuery(node).find('x[xmlns="' + NS_DELAY + '"]:first').attr('stamp');
if(x_delay)
delay = x_delay.replace(/^(\w{4})(\w{2})(\w{2})T(\w{2}):(\w{2}):(\w{2})Z?(\S+)?/, '$1-$2-$3T$4:$5:$6Z$7');
}
return delay;
}
/*
Jappix - An open social platform
These are the CAPS JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou, Maranda
Last revision: 20/02/13
*/
// Returns an array of the Jappix disco#infos
function myDiscoInfos() {
var fArray = new Array(
NS_MUC,
NS_MUC_USER,
NS_MUC_ADMIN,
NS_MUC_OWNER,
NS_MUC_CONFIG,
NS_DISCO_INFO,
NS_DISCO_ITEMS,
NS_PUBSUB_RI,
NS_BOSH,
NS_CAPS,
NS_MOOD,
NS_ACTIVITY,
NS_TUNE,
NS_GEOLOC,
NS_NICK,
NS_URN_ADATA,
NS_URN_AMETA,
NS_URN_MBLOG,
NS_URN_INBOX,
NS_MOOD + NS_NOTIFY,
NS_ACTIVITY + NS_NOTIFY,
NS_TUNE + NS_NOTIFY,
NS_GEOLOC + NS_NOTIFY,
NS_URN_MBLOG + NS_NOTIFY,
NS_URN_INBOX + NS_NOTIFY,
NS_URN_DELAY,
NS_ROSTER,
NS_ROSTERX,
NS_HTTP_AUTH,
NS_CHATSTATES,
NS_XHTML_IM,
NS_URN_MAM,
NS_IPV6,
NS_LAST,
NS_PRIVATE,
NS_REGISTER,
NS_SEARCH,
NS_COMMANDS,
NS_VERSION,
NS_XDATA,
NS_VCARD,
NS_URN_TIME,
NS_URN_PING,
NS_URN_RECEIPTS,
NS_PRIVACY,
NS_IQOOB,
NS_XOOB
);
return fArray;
}
// Gets the disco#infos of an entity
function getDiscoInfos(to, caps) {
// No CAPS
if(!caps) {
logThis('No CAPS: ' + to, 2);
displayDiscoInfos(to, '');
return false;
}
// Get the stored disco infos
var xml = XMLFromString(getPersistent('global', 'caps', caps));
// Yet stored
if(xml) {
logThis('CAPS from cache: ' + to, 3);
displayDiscoInfos(to, xml);
return true;
}
logThis('CAPS from the network: ' + to, 3);
// Not stored: get the disco#infos
var iq = new JSJaCIQ();
iq.setTo(to);
iq.setType('get');
iq.setQuery(NS_DISCO_INFO);
con.send(iq, handleDiscoInfos);
return true;
}
// Handles the disco#infos of an entity
function handleDiscoInfos(iq) {
if(!iq || (iq.getType() == 'error'))
return;
// IQ received, get some values
var from = fullXID(getStanzaFrom(iq));
var query = iq.getQuery();
// Generate the CAPS-processing values
var identities = new Array();
var features = new Array();
var data_forms = new Array();
// Identity values
$(query).find('identity').each(function() {
var pCategory = $(this).attr('category');
var pType = $(this).attr('type');
var pLang = $(this).attr('xml:lang');
var pName = $(this).attr('name');
if(!pCategory)
pCategory = '';
if(!pType)
pType = '';
if(!pLang)
pLang = '';
if(!pName)
pName = '';
identities.push(pCategory + '/' + pType + '/' + pLang + '/' + pName);
});
// Feature values
$(query).find('feature').each(function() {
var pVar = $(this).attr('var');
// Add the current value to the array
if(pVar)
features.push(pVar);
});
// Data-form values
$(query).find('x[xmlns="' + NS_XDATA + '"]').each(function() {
// Initialize some stuffs
var pString = '';
var sortVar = new Array();
// Add the form type field
$(this).find('field[var="FORM_TYPE"] value').each(function() {
var cText = $(this).text();
if(cText)
pString += cText + '<';
});
// Add the var attributes into an array
$(this).find('field:not([var="FORM_TYPE"])').each(function() {
var cVar = $(this).attr('var');
if(cVar)
sortVar.push(cVar);
});
// Sort the var attributes
sortVar = sortVar.sort();
// Loop this sorted var attributes
for(i in sortVar) {
// Initialize the value sorting
var sortVal = new Array();
// Append it to the string
pString += sortVar[i] + '<';
// Add each value to the array
$(this).find('field[var=' + sortVar[i] + '] value').each(function() {
sortVal.push($(this).text());
});
// Sort the values
sortVal = sortVal.sort();
// Append the values to the string
for(j in sortVal)
pString += sortVal[j] + '<';
}
// Any string?
if(pString) {
// Remove the undesired double '<' from the string
if(pString.match(/(.+)(<)+$/))
pString = pString.substring(0, pString.length - 1);
// Add the current string to the array
data_forms.push(pString);
}
});
// Process the CAPS
var caps = processCaps(identities, features, data_forms);
// Get the XML string
var xml = xmlToString(query);
// Store the disco infos
setPersistent('global', 'caps', caps, xml);
// This is our server
if(from == getServer()) {
// Handle the features
handleFeatures(xml);
logThis('Got our server CAPS', 3);
}
else {
// Display the disco infos
displayDiscoInfos(from, xml);
logThis('Got CAPS: ' + from, 3);
}
}
// Displays the disco#infos everywhere needed for an entity
function displayDiscoInfos(from, xml) {
// Generate the chat path
var xid = bareXID(from);
// This comes from a private groupchat chat?
if(isPrivate(xid))
xid = from;
hash = hex_md5(xid);
// Support indicators
var xhtml_im = false;
var iq_oob = false;
var x_oob = false;
var receipts = false;
// Display the supported features
$(xml).find('feature').each(function() {
var current = $(this).attr('var');
// xHTML-IM
if(current == NS_XHTML_IM)
xhtml_im = true;
// Out of Band Data (IQ)
if(current == NS_IQOOB)
iq_oob = true;
// Out of Band Data (X)
if(current == NS_XOOB)
x_oob = true;
// Receipts
else if(current == NS_URN_RECEIPTS)
receipts = true;
});
// Paths
var path = $('#' + hash);
var message_area = path.find('.message-area');
var style = path.find('.chat-tools-style');
var file = path.find('.chat-tools-file');
// Apply xHTML-IM
if(xhtml_im)
style.show();
else {
// Remove the tooltip elements
style.hide();
style.find('.bubble-style').remove();
// Reset the markers
message_area.removeAttr('style')
.removeAttr('data-font')
.removeAttr('data-fontsize')
.removeAttr('data-color')
.removeAttr('data-bold')
.removeAttr('data-italic')
.removeAttr('data-underline');
}
// Apply Out of Band Data
if(iq_oob || x_oob) {
file.show();
// Set a marker
if(iq_oob)
file.attr('data-oob', 'iq');
else
file.attr('data-oob', 'x');
}
else {
// Remove the tooltip elements
file.hide();
file.find('.bubble-style').remove();
// Reset the marker
file.removeAttr('data-oob');
}
// Apply receipts
if(receipts)
message_area.attr('data-receipts', 'true');
else
message_area.removeAttr('data-receipts');
}
// Generates the CAPS hash
function processCaps(cIdentities, cFeatures, cDataForms) {
// Initialize
var cString = '';
// Sort the arrays
cIdentities = cIdentities.sort();
cFeatures = cFeatures.sort();
cDataForms = cDataForms.sort();
// Process the sorted identity string
for(a in cIdentities)
cString += cIdentities[a] + '<';
// Process the sorted feature string
for(b in cFeatures)
cString += cFeatures[b] + '<';
// Process the sorted data-form string
for(c in cDataForms)
cString += cDataForms[c] + '<';
// Process the SHA-1 hash
var cHash = b64_sha1(cString);
return cHash;
}
// Generates the Jappix CAPS hash
function myCaps() {
return processCaps(new Array('client/web//Jappix'), myDiscoInfos(), new Array());
}
/*
Jappix - An open social platform
These are the vCard JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou, Maranda
Last revision: 20/02/12
*/
// Opens the vCard popup
function openVCard() {
// Popup HTML content
var html =
'<div class="top">' + _e("Your profile") + '</div>' +
'<div class="tab">' +
'<a href="#" class="tab-active" data-key="1">' + _e("Identity") + '</a>' +
'<a href="#" data-key="2">' + _e("Profile image") + '</a>' +
'<a href="#" data-key="3">' + _e("Others") + '</a>' +
'</div>' +
'<div class="content">' +
'<div id="lap1" class="lap-active one-lap forms">' +
'<fieldset>' +
'<legend>' + _e("Personal") + '</legend>' +
'<label for="USER-FN">' + _e("Complete name") + '</label>' +
'<input type="text" id="USER-FN" class="vcard-item" placeholder="John Locke" />' +
'<label for="USER-NICKNAME">' + _e("Nickname") + '</label>' +
'<input type="text" id="USER-NICKNAME" class="vcard-item" placeholder="Jo" />' +
'<label for="USER-N-GIVEN">' + _e("First name") + '</label>' +
'<input type="text" id="USER-N-GIVEN" class="vcard-item" placeholder="John" />' +
'<label for="USER-N-FAMILY">' + _e("Last name") + '</label>' +
'<input type="text" id="USER-N-FAMILY" class="vcard-item" placeholder="Locke" />' +
'<label for="USER-BDAY">' + _e("Date of birth") + '</label>' +
'<input type="text" id="USER-BDAY" class="vcard-item" pattern="^[0-9]{2}-[0-9]{2}-[0-9]{4}$" placeholder="16-02-1974" />' +
'</fieldset>' +
'<fieldset>' +
'<legend>' + _e("Contact") + '</legend>' +
'<label for="USER-EMAIL-USERID">' + _e("E-mail") + '</label>' +
'<input type="text" id="USER-EMAIL-USERID" class="vcard-item" placeholder="john@locke.fam" />' +
'<label for="USER-TEL-NUMBER">' + _e("Phone") + '</label>' +
'<input type="text" id="USER-TEL-NUMBER" class="vcard-item" placeholder="John" placeholder="+1-292-321-0812" />' +
'<label for="USER-URL">' + _e("Website") + '</label>' +
'<input type="text" id="USER-URL" class="vcard-item" placeholder="john.locke.fam" />' +
'</fieldset>' +
'</div>' +
'<div id="lap2" class="one-lap forms">' +
'<fieldset>' +
'<legend>' + _e("New") + '</legend>' +
'<input type="hidden" id="USER-PHOTO-TYPE" class="vcard-item" />' +
'<input type="hidden" id="USER-PHOTO-BINVAL" class="vcard-item" />' +
'<form id="vcard-avatar" action="./php/avatar-upload.php" method="post" enctype="multipart/form-data">' +
generateFileShare() +
'</form>' +
'</fieldset>' +
'<fieldset>' +
'<legend>' + _e("Current") + '</legend>' +
'<div class="avatar-container"></div>' +
'<a href="#" class="one-button avatar-delete talk-images">' + _e("Delete") + '</a>' +
'<div class="no-avatar">' + _e("What a pity! You have no profile image defined in your identity card!") + '</div>' +
'</fieldset>' +
'<div class="avatar-wait avatar-info">' + _e("Please wait while your avatar is uploaded...") + '</div>' +
'<div class="avatar-ok avatar-info">' + _e("Here it is! A new beautiful profile image!") + '</div>' +
'<div class="avatar-error avatar-info">' + _e("The image file is not supported or has a bad size.") + '</div>' +
'</div>' +
'<div id="lap3" class="one-lap forms">' +
'<fieldset>' +
'<legend>' + _e("Address") + '</legend>' +
'<label for="USER-ADR-STREET">' + _e("Street") + '</label>' +
'<input type="text" id="USER-ADR-STREET" class="vcard-item" placeholder="Manhattan" />' +
'<label for="USER-ADR-LOCALITY">' + _e("City") + '</label>' +
'<input type="text" id="USER-ADR-LOCALITY" class="vcard-item" placeholder="New-York" />' +
'<label for="USER-ADR-PCODE">' + _e("Postal code") + '</label>' +
'<input type="text" id="USER-ADR-PCODE" class="vcard-item" placeholder="10002" />' +
'<label for="USER-ADR-CTRY">' + _e("Country") + '</label>' +
'<input type="text" id="USER-ADR-CTRY" class="vcard-item" placeholder="USA" />' +
'</fieldset>' +
'<fieldset>' +
'<legend>' + _e("Biography") + '</legend>' +
'<textarea id="USER-DESC" rows="8" cols="60" class="vcard-item"></textarea>' +
'</fieldset>' +
'</div>' +
'<div class="infos">' +
'<p class="infos-title">' + _e("Important notice") + '</p>' +
'<p>' + _e("Be careful with the information you store into your profile, because it might be accessible by everyone (even someone you don't want to).") + '</p>' +
'<p>' + _e("Not everything is private on XMPP; this is one of those things, your public profile (vCard).") + '</p>' +
'<p>' + printf(_e("It is strongly recommended to upload a profile image (%s maximum), like a picture of yourself, because that makes you easily recognizable by your friends."), JAPPIX_MAX_UPLOAD) + '</p>' +
'<p><b><a href="https://me.jappix.com/new" target="_blank">' + _e("Enable my public profile") + ' »</a></b></p>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish save disabled">' + _e("Save") + '</a>' +
'<a href="#" class="finish cancel">' + _e("Cancel") + '</a>' +
'</div>';
// Create the popup
createPopup('vcard', html);
// Associate the events
launchVCard();
// We get the VCard informations
getVCard(getXID(), 'user');
return false;
}
// Closes the vCard popup
function closeVCard() {
// Destroy the popup
destroyPopup('vcard');
// Create the welcome end popup?
if(END_WELCOME) {
// Open popup
openMe();
// Unavoidable popup
$('#me').addClass('unavoidable');
}
return false;
}
// Switches the vCard popup tabs
function switchVCard(id) {
$('#vcard .one-lap').removeClass('lap-active');
$('#vcard #lap' + id).addClass('lap-active');
$('#vcard .tab a').removeClass('tab-active');
$('#vcard .tab a[data-key="' + id + '"]').addClass('tab-active');
return false;
}
// Waits for the avatar upload reply
function waitAvatarUpload() {
// Reset the avatar info
$('#vcard .avatar-info').hide().stopTime();
// Show the wait info
$('#vcard .avatar-wait').show();
}
// Handles the avatar upload reply
function handleAvatarUpload(responseXML) {
// Data selector
var dData = $(responseXML).find('jappix');
// Not current upload session?
if(parseInt(dData.attr('id')) != parseInt($('#vcard-avatar input[name="id"]').val()))
return;
// Reset the avatar info
$('#vcard .avatar-info').hide().stopTime();
// Process the returned data
if(!dData.find('error').size()) {
// Read the values
var aType = dData.find('type').text();
var aBinval = dData.find('binval').text();
// We remove everything that isn't useful right here
$('#vcard .no-avatar').hide();
$('#vcard .avatar').remove();
// We display the delete button
$('#vcard .avatar-delete').show();
// We tell the user it's okay
$('#vcard .avatar-ok').show();
// Timer
$('#vcard .avatar-info').oneTime('10s', function() {
$(this).hide();
});
// We put the base64 values in a hidden input to be sent
$('#USER-PHOTO-TYPE').val(aType);
$('#USER-PHOTO-BINVAL').val(aBinval);
// We display the avatar !
$('#vcard .avatar-container').replaceWith('<div class="avatar-container"><img class="avatar" src="data:' + aType + ';base64,' + aBinval + '" alt="" /></div>');
}
// Any error?
else {
$('#vcard .avatar-error').show();
// Timer
$('#vcard .avatar-info').oneTime('10s', function() {
$(this).hide();
});
logThis('Error while uploading the avatar: ' + dData.find('error').text(), 1);
}
}
// Deletes the encoded avatar of an user
function deleteAvatar() {
// We remove the avatar displayed elements
$('#vcard .avatar-info').stopTime();
$('#vcard .avatar-info, #vcard .avatar-wait, #vcard .avatar-error, #vcard .avatar-ok, #vcard .avatar-delete').hide();
$('#vcard .avatar').remove();
// We reset the input value
$('#USER-PHOTO-TYPE, #USER-PHOTO-BINVAL').val('');
// We show the avatar-uploading request
$('#vcard .no-avatar').show();
return false;
}
// Creates a special vCard input
function createInputVCard(id, type) {
// Generate the new ID
id = 'USER-' + id;
// Can append the content
if((type == 'user') && !exists('#vcard #' + id))
$('#vcard .content').append('<input id="' + id + '" class="vcard-item" type="hidden" />');
}
// Gets the vCard of a XID
function getVCard(to, type) {
// Generate a special ID
var id = genID();
// New IQ
var iq = new JSJaCIQ();
iq.setID(id);
iq.setType('get');
iq.appendNode('vCard', {'xmlns': NS_VCARD});
// Send the IQ to the good user
if(type == 'user') {
// Show the wait icon
$('#vcard .wait').show();
// Apply the session ID
$('#vcard').attr('data-vcard', id);
// Send the IQ
con.send(iq, handeUVCard);
}
else {
// Show the wait icon
$('#userinfos .wait').show();
// Apply the session ID
$('#userinfos').attr('data-vcard', id);
// Send the IQ
iq.setTo(to);
con.send(iq, handeBVCard);
}
}
// Handles the current connected user's vCard
function handeUVCard(iq) {
handleVCard(iq, 'user');
}
// Handles an external buddy vCard
function handeBVCard(iq) {
handleVCard(iq, 'buddy');
}
// Handles a vCard stanza
function handleVCard(iq, type) {
// Extract the data
var iqID = iq.getID();
var iqFrom = fullXID(getStanzaFrom(iq));
var iqNode = iq.getNode();
// Define some paths
var path_vCard = '#vcard[data-vcard="' + iqID + '"]';
var path_userInfos = '#userinfos[data-vcard="' + iqID + '"]';
// End if the session does not exist
if(((type == 'user') && !exists(path_vCard)) || ((type == 'buddy') && !exists(path_userInfos)))
return;
// We retrieve main values
var values_yet = [];
$(iqNode).find('vCard').children().each(function() {
// Read the current parent node name
var tokenname = (this).nodeName.toUpperCase();
// Node with a parent
if($(this).children().size()) {
$(this).children().each(function() {
// Get the node values
var currentID = tokenname + '-' + (this).nodeName.toUpperCase();
var currentText = $(this).text();
// Not yet added?
if(!existArrayValue(values_yet, currentID)) {
// Create an input if it does not exist
createInputVCard(values_yet, type);
// Userinfos viewer popup
if((type == 'buddy') && currentText) {
if(currentID == 'EMAIL-USERID')
$(path_userInfos + ' #BUDDY-' + currentID).html('<a href="mailto:' + currentText.htmlEnc() + '" target="_blank">' + currentText.htmlEnc() + '</a>');
else
$(path_userInfos + ' #BUDDY-' + currentID).text(currentText.htmlEnc());
}
// Profile editor popup
else if(type == 'user')
$(path_vCard + ' #USER-' + currentID).val(currentText);
// Avoid duplicating the value
values_yet.push(currentID);
}
});
}
// Node without any parent
else {
// Get the node values
var currentText = $(this).text();
// Not yet added?
if(!existArrayValue(values_yet, tokenname)) {
// Create an input if it does not exist
createInputVCard(tokenname, type);
// Userinfos viewer popup
if((type == 'buddy') && currentText) {
// URL modification
if(tokenname == 'URL') {
// No http:// or https:// prefix, we should add it
if(!currentText.match(/^https?:\/\/(.+)/))
currentText = 'http://' + currentText;
currentText = '<a href="' + currentText + '" target="_blank">' + currentText.htmlEnc() + '</a>';
}
// Description modification
else if(tokenname == 'DESC')
currentText = filterThisMessage(currentText, getBuddyName(iqFrom).htmlEnc(), true);
// Other stuffs
else
currentText = currentText.htmlEnc();
$(path_userInfos + ' #BUDDY-' + tokenname).html(currentText);
}
// Profile editor popup
else if(type == 'user')
$(path_vCard + ' #USER-' + tokenname).val(currentText);
// Avoid duplicating the value
values_yet.push(tokenname);
}
}
});
// Update the stored avatar
if(type == 'buddy') {
// Get the avatar XML
var xml = getPersistent('global', 'avatar', iqFrom);
// If there were no stored avatar previously
if($(XMLFromString(xml)).find('type').text() == 'none') {
xml = xml.replace(/<forced>false<\/forced>/gi, '<forced>true</forced>');
setPersistent(getXID(), 'avatar', iqFrom, xml);
}
// Handle the user avatar
handleAvatar(iq);
}
// The avatar values targets
var aBinval, aType, aContainer;
if(type == 'user') {
aBinval = $('#USER-PHOTO-BINVAL').val();
aType = $('#USER-PHOTO-TYPE').val();
aContainer = path_vCard + ' .avatar-container';
}
else {
aBinval = $(iqNode).find('BINVAL:first').text();
aType = $(iqNode).find('TYPE:first').text();
aContainer = path_userInfos + ' .avatar-container';
}
// We display the avatar if retrieved
if(aBinval) {
// No type?
if(!aType)
aType = 'image/png';
if(type == 'user') {
// We move all the things that we don't need in that case
$(path_vCard + ' .no-avatar').hide();
$(path_vCard + ' .avatar-delete').show();
$(path_vCard + ' .avatar').remove();
}
// We display the avatar we have just received
$(aContainer).replaceWith('<div class="avatar-container"><img class="avatar" src="data:' + aType + ';base64,' + aBinval + '" alt="" /></div>');
}
else if(type == 'buddy')
$(aContainer).replaceWith('<div class="avatar-container"><img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" /></div>');
// Do someting depending of the type
if(type == 'user') {
$(path_vCard + ' .wait').hide();
$(path_vCard + ' .finish:first').removeClass('disabled');
}
else
vCardBuddyInfos();
logThis('vCard received: ' + iqFrom);
}
// Sends the vCard of the user
function sendVCard() {
// Initialize the IQ
var iq = new JSJaCIQ();
iq.setType('set');
var vCard = iq.appendNode('vCard', {'xmlns': NS_VCARD});
// We send the identity part of the form
$('#vcard .vcard-item').each(function() {
var itemID = $(this).attr('id').replace(/^USER-(.+)/, '$1');
var itemVal = $(this).val();
if(itemVal && itemID) {
if(itemID.indexOf('-') != -1) {
var tagname = explodeThis('-', itemID, 0);
var aNode;
if(vCard.getElementsByTagName(tagname).length > 0)
aNode = vCard.getElementsByTagName(tagname).item(0);
else
aNode = vCard.appendChild(iq.buildNode(tagname, {'xmlns': NS_VCARD}));
aNode.appendChild(iq.buildNode(explodeThis('-', itemID, 1), {'xmlns': NS_VCARD}, itemVal));
}
else
vCard.appendChild(iq.buildNode(itemID, {'xmlns': NS_VCARD}, itemVal));
}
});
// Send the IQ
con.send(iq);
// Send the user nickname & avatar over PEP
if(enabledPEP()) {
// Read values
var user_nick = $('#USER-NICKNAME').val();
var photo_bin = $('#USER-PHOTO-BINVAL').val();
var photo_type = $('#USER-PHOTO-TYPE').val();
var photo_data = Base64.decode(photo_bin) || '';
var photo_bytes = photo_data.length || '';
var photo_id = hex_sha1(photo_data) || '';
// Values array
var read = [
user_nick,
photo_bin,
[photo_type, photo_id, photo_bytes]
];
// Nodes array
var node = [
NS_NICK,
NS_URN_ADATA,
NS_URN_AMETA
];
// Generate the XML
for(i in read) {
var iq = new JSJaCIQ();
iq.setType('set');
var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
var publish = pubsub.appendChild(iq.buildNode('publish', {'node': node[i], 'xmlns': NS_PUBSUB}));
if((i == 0) && read[0]) {
var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
// Nickname element
item.appendChild(iq.buildNode('nick', {'xmlns': NS_NICK}, read[i]));
}
else if(((i == 1) || (i == 2)) && read[1]) {
var item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB}));
// Apply the SHA-1 hash
if(photo_id)
item.setAttribute('id', photo_id);
// Avatar data node
if(i == 1)
item.appendChild(iq.buildNode('data', {'xmlns': NS_URN_ADATA}, read[i]));
// Avatar metadata node
else {
var metadata = item.appendChild(iq.buildNode('metadata', {'xmlns': NS_URN_AMETA}));
if(read[i]) {
var meta_info = metadata.appendChild(iq.buildNode('info', {'xmlns': NS_URN_AMETA}));
if(read[i][0])
meta_info.setAttribute('type', read[i][0]);
if(read[i][1])
meta_info.setAttribute('id', read[i][1]);
if(read[i][2])
meta_info.setAttribute('bytes', read[i][2]);
}
}
}
con.send(iq);
}
}
// Close the vCard stuffs
closeVCard();
// Get our new avatar
getAvatar(getXID(), 'force', 'true', 'forget');
logThis('vCard sent.');
return false;
}
// Plugin launcher
function launchVCard() {
// Focus on the first input
$(document).oneTime(10, function() {
$('#vcard input:first').focus();
});
// Keyboard events
$('#vcard input[type="text"]').keyup(function(e) {
// Enter pressed: send the vCard
if((e.keyCode == 13) && !$('#vcard .finish.save').hasClass('disabled'))
return sendVCard();
});
// Click events
$('#vcard .tab a').click(function() {
// Yet active?
if($(this).hasClass('tab-active'))
return false;
// Switch to the good tab
var key = parseInt($(this).attr('data-key'));
return switchVCard(key);
});
$('#vcard .avatar-delete').click(function() {
return deleteAvatar();
});
$('#vcard .bottom .finish').click(function() {
if($(this).is('.cancel'))
return closeVCard();
if($(this).is('.save') && !$(this).hasClass('disabled'))
return sendVCard();
return false;
});
// Avatar upload vars
var avatar_options = {
dataType: 'xml',
beforeSubmit: waitAvatarUpload,
success: handleAvatarUpload
};
// Avatar upload form submit event
$('#vcard-avatar').submit(function() {
if($('#vcard .wait').is(':hidden') && $('#vcard .avatar-info.avatar-wait').is(':hidden') && $('#vcard-avatar input[type="file"]').val())
$(this).ajaxSubmit(avatar_options);
return false;
});
// Avatar upload input change event
$('#vcard-avatar input[type="file"]').change(function() {
if($('#vcard .wait').is(':hidden') && $('#vcard .avatar-info.avatar-wait').is(':hidden') && $(this).val())
$('#vcard-avatar').ajaxSubmit(avatar_options);
return false;
});
// Placeholders
$('#vcard-avatar input[type="text"]').placeholder();
}
/*
Jappix - An open social platform
These are the user-infos JS scripts for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 27/03/11
*/
// Opens the user-infos popup
function openUserInfos(xid) {
// Can show shortcuts?
var shortcuts = '';
if(xid != getXID()) {
shortcuts = '<div class="shortcuts">' +
'<a href="#" class="message talk-images" title="' + _e("Send him/her a message") + '" onclick="closeUserInfos(); return composeInboxMessage(\'' + encodeOnclick(xid) + '\');"></a>' +
'<a href="#" class="chat talk-images" title="' + _e("Start a chat with him/her") + '" onclick="closeUserInfos(); return checkChatCreate(\'' + encodeOnclick(xid) + '\', \'chat\');"></a>' +
'<a href="#" class="command talk-images" title="' + _e("Command") + '" onclick="closeUserInfos(); return retrieveAdHoc(\'' + encodeOnclick(xid) + '\');"></a>' +
'</div>';
}
// Popup HTML content
var html =
'<div class="top">' + _e("User profile") + '</div>' +
'<div class="tab">' +
'<a href="#" class="tab-active" data-key="1">' + _e("General") + '</a>' +
'<a href="#" data-key="2">' + _e("Advanced") + '</a>' +
'<a href="#" data-key="3">' + _e("Comments") + '</a>' +
'</div>' +
'<div class="content">' +
'<div class="lap-active one-lap info1">' +
'<div class="main-infos">' +
'<div class="avatar-container">' +
'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
'</div>' +
'<h1 id="BUDDY-FN" class="reset-info">' + _e("unknown") + '</h1>' +
'<h2 class="buddy-xid" class="reset-info">' + _e("unknown") + '</h2>' +
'<h3 class="buddy-last" class="reset-info">' + _e("unknown") + '</h3>' +
shortcuts +
'</div>' +
'<div class="block-infos">' +
'<div class="one-line"><b class="line-label">' + _e("Date of birth") + '</b><span id="BUDDY-BDAY" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("E-mail") + '</b><span id="BUDDY-EMAIL-USERID" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("Phone") + '</b><span id="BUDDY-TEL-NUMBER" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("Website") + '</b><span id="BUDDY-URL" class="reset-info">' + _e("unknown") + '</span></div>' +
'</div>' +
'<div class="block-infos">' +
'<div class="one-line"><b class="line-label">' + _e("Client") + '</b><span id="BUDDY-CLIENT" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("System") + '</b><span id="BUDDY-SYSTEM" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("Local time") + '</b><span id="BUDDY-TIME" class="reset-info">' + _e("unknown") + '</span></div>' +
'</div>' +
'</div>' +
'<div class="one-lap info2">' +
'<div class="block-infos">' +
'<div class="one-line"><b class="line-label">' + _e("Street") + '</b><span id="BUDDY-ADR-STREET" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("City") + '</b><span id="BUDDY-ADR-LOCALITY" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("Postal code") + '</b><span id="BUDDY-ADR-PCODE" class="reset-info">' + _e("unknown") + '</span></div>' +
'<div class="one-line"><b class="line-label">' + _e("Country") + '</b><span id="BUDDY-ADR-CTRY" class="reset-info">' + _e("unknown") + '</span></div>' +
'</div>' +
'<div class="block-infos">' +
'<div class="one-line"><b class="line-label">' + _e("Biography") + '</b><span id="BUDDY-DESC" class="reset-info">' + _e("unknown") + '</span></div>' +
'</div>' +
'</div>' +
'<div class="one-lap info3">' +
'<textarea class="rosternotes" rows="8" cols="60"></textarea>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('userinfos', html);
// Associate the events
launchUserInfos();
// We retrieve the user's vcard
retrieveUserInfos(xid);
return false;
}
// Closes the user-infos popup
function closeUserInfos() {
// Send the buddy comments
sendBuddyComments();
// Destroy the popup
destroyPopup('userinfos');
return false;
}
// Gets the user-infos
function retrieveUserInfos(xid) {
// We setup the waiting indicator
markers = 'vcard last';
// We put the user's XID
$('#userinfos .buddy-xid').text(xid);
// We get the vCard
getVCard(xid, 'buddy');
// Get the highest resource for this XID
var cXID = highestPriority(xid);
var pXID = xid;
// If the user is logged in
if(cXID) {
// Change the XID
pXID = cXID;
// We request the user's system infos
queryUserInfos(cXID, 'version')
// We request the user's local time
queryUserInfos(cXID, 'time')
// Add these to the markers
markers += ' version time';
}
// We request the user's last activity
queryUserInfos(pXID, 'last');
// Add the markers
$('#userinfos .content').addClass(markers);
// We request all the user's comments
displayBuddyComments(xid);
}
// Builds the asked user-infos query
function queryUserInfos(xid, mode) {
// Generate a session ID
var id = genID();
$('#userinfos').attr('data-' + mode, id);
// New IQ
var iq = new JSJaCIQ();
iq.setID(id);
iq.setType('get');
iq.setTo(xid);
// Last activity query
if(mode == 'last') {
iq.setQuery(NS_LAST);
con.send(iq, lastActivityUserInfos);
}
// Time query
else if(mode == 'time') {
iq.appendNode('time', {'xmlns': NS_URN_TIME});
con.send(iq, localTimeUserInfos);
}
// Version query
else if(mode == 'version') {
iq.setQuery(NS_VERSION);
con.send(iq, versionUserInfos);
}
}
// Checks if the waiting item can be hidden
function vCardBuddyInfos() {
$('#userinfos .content').removeClass('vcard');
wUserInfos();
}
// Displays the buddy comments
function displayBuddyComments(xid) {
// We get the value in the database
var value = getDB('rosternotes', xid);
// Display the value
if(value)
$('#userinfos .rosternotes').val(value);
}
// Displays the user's last activity result
function lastActivityUserInfos(iq) {
// Extract the request ID
var id = iq.getID();
var path = '#userinfos[data-last="' + id + '"]';
// End if session does not exist
if(!exists(path))
return;
if(iq && (iq.getType() == 'result')) {
// Get the values
var from = fullXID(getStanzaFrom(iq));
var seconds = $(iq.getNode()).find('query').attr('seconds');
// Any seconds?
if(seconds != undefined) {
// Initialize the parsing
var last;
seconds = parseInt(seconds);
// Active user
if(seconds <= 60)
last = _e("User currently active");
// Inactive user
else {
// Parse the date
var date_now = new Date();
var time_now = date_now.getTime();
var date_last = new Date(date_now - (seconds * 1000));
var date = date_last.toLocaleString();
// Offline user
if(from.indexOf('/') == -1)
last = printf(_e("Last seen: %s"), date);
// Online user
else
last = printf(_e("Inactive since: %s"), date);
}
// Append this text
$('#userinfos .buddy-last').text(last);
}
logThis('Last activity received: ' + from);
}
$('#userinfos .content').removeClass('last');
wUserInfos();
}
// Displays the user's software version result
function versionUserInfos(iq) {
// Extract the request ID
var id = iq.getID();
var path = '#userinfos[data-version="' + id + '"]';
// End if session does not exist
if(!exists(path))
return;
// Extract the reply data
if(iq && (iq.getType() == 'result')) {
// Get the values
var xml = iq.getQuery();
var name = $(xml).find('name').text();
var version = $(xml).find('version').text();
var os = $(xml).find('os').text();
// Put the values together
if(name && version)
name = name + ' ' + version;
// Display the values
if(name)
$(path + ' #BUDDY-CLIENT').text(name);
if(os)
$(path + ' #BUDDY-SYSTEM').text(os);
logThis('Software version received: ' + fullXID(getStanzaFrom(iq)));
}
$('#userinfos .content').removeClass('version');
wUserInfos();
}
// Displays the user's local time result
function localTimeUserInfos(iq) {
// Extract the request ID
var id = iq.getID();
var path = '#userinfos[data-time="' + id + '"]';
// End if session does not exist
if(!exists(path))
return;
if(iq && (iq.getType() == 'result')) {
// Get the values
var xml = iq.getNode();
var tzo = $(xml).find('tzo').text();
var utc = $(xml).find('utc').text();
// Any UTC?
if(utc) {
// Add the TZO if there's no one
if(tzo && utc.match(/^(.+)Z$/))
utc = RegExp.$1 + tzo;
// Get the local date string
var local_string = Date.hrTime(utc);
// Then display it
$(path + ' #BUDDY-TIME').text(local_string);
}
logThis('Local time received: ' + fullXID(getStanzaFrom(iq)));
}
$('#userinfos .content').removeClass('time');
wUserInfos();
}
// Hides the waiting image if needed
function wUserInfos() {
var selector = $('#userinfos .content');
if(!selector.hasClass('vcard') && !selector.hasClass('last') && !selector.hasClass('version') && !selector.hasClass('time'))
$('#userinfos .wait').hide();
}
// Sends the buddy comments
function sendBuddyComments() {
// Update the current value
var value = $('#userinfos .rosternotes').val();
var xid = $('#userinfos .buddy-xid').text();
// Necessary to update?
var old_value = getDB('rosternotes', xid);
if((old_value == value) || (!old_value && !value))
return false;
// Update the database
setDB('rosternotes', xid, value);
// Send the new buddy storage values
var iq = new JSJaCIQ();
iq.setType('set');
var query = iq.setQuery(NS_PRIVATE);
var storage = query.appendChild(iq.buildNode('storage', {'xmlns': NS_ROSTERNOTES}));
// We regenerate the XML
for(var i = 0; i < storageDB.length; i++) {
// Get the pointer values
var current = storageDB.key(i);
// If the pointer is on a stored rosternote
if(explodeThis('_', current, 0) == 'rosternotes') {
var xid = explodeThis('_', current, 1);
var value = storageDB.getItem(current);
if(xid && value)
storage.appendChild(iq.buildNode('note', {'jid': xid, 'xmlns': NS_ROSTERNOTES}, value));
}
}
con.send(iq);
return false;
}
// Switches the user-infos tabs
function switchUInfos(id) {
$('#userinfos .content .one-lap').hide();
$('#userinfos .content .info' + id).show();
$('#userinfos .tab a').removeClass('tab-active');
$('#userinfos .tab a[data-key="' + id + '"]').addClass('tab-active');
return false;
}
// Gets the user's informations when creating a new chat
function getUserInfos(hash, xid, nick, type) {
// This is a normal chat
if(type != 'private') {
// Display the buddy name
if(nick) {
$('#' + hash + ' .top .name .bc-name').text(nick);
$('#page-switch .' + hash + ' .name').text(nick);
}
// Get the buddy PEP informations
displayAllPEP(xid);
}
// Display the buddy presence
presenceFunnel(xid, hash);
}
// Plugin launcher
function launchUserInfos() {
// Click events
$('#userinfos .tab a').click(function() {
// Yet active?
if($(this).hasClass('tab-active'))
return false;
// Switch to the good tab
var key = parseInt($(this).attr('data-key'));
return switchUInfos(key);
});
$('#userinfos .bottom .finish').click(function() {
return closeUserInfos();
});
}
/*
Jappix - An open social platform
These are the seach tools JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 19/03/11
*/
// Searches in the user's buddy list
function processBuddySearch(query) {
// No query submitted?
if(!query)
return;
// Wildcard (*) submitted
if(query == '*')
query = '';
// Replace forbidden characters in regex
query = escapeRegex(query);
// Create an empty array
var results = new Array();
// Search regex
var regex = new RegExp('((^)|( ))' + query, 'gi');
// Search in the roster
var buddies = getAllBuddies();
for(i in buddies) {
var xid = buddies[i];
var nick = getBuddyName(xid);
// Buddy match our search, and not yet in the array
if(nick.match(regex) && !existArrayValue(results, xid))
results.push(xid);
}
// Return the results array
return results;
}
// Resets the buddy search tool
function resetBuddySearch(destination) {
$(destination + ' ul').remove();
$(destination + ' input').removeClass('suggested');
}
// Add the clicked XID to the input
function addBuddySearch(destination, xid) {
// Remove the search tool
resetBuddySearch(destination);
// Define a selector
var input = $(destination + ' input');
var value = input.val();
// Get the old value (if there's another value)
var old = '';
if(value.match(/(^(.+)(,)(\s)?)(\w+)$/))
old = RegExp.$1;
// Add the XID to the "to" input and focus on it
$(document).oneTime(10, function() {
input.val(old + xid).focus();
});
return false;
}
// Creates the appropriate markup for the search results
function createBuddySearch(destination) {
// Reset the search engine
resetBuddySearch(destination);
// Get the entered value
var value = $(destination + ' input').val();
// Separation with a comma?
if(value.match(/^(.+)((,)(\s)?)(\w+)$/))
value = RegExp.$5;
// Get the result array
var entered = processBuddySearch(value);
// Display each result (if any)
if(entered && entered.length) {
// Set a special class to the search input
$(destination + ' input').addClass('suggested');
// Append each found buddy in the container
var regex = new RegExp('((^)|( ))' + value, 'gi');
// Initialize the code generation
var code = '<ul>';
for(b in entered) {
// Get some values from the XID
var current = getBuddyName(entered[b]).htmlEnc();
current = current.replace(regex, '<b>$&</b>');
// Add the current element to the global code
code += '<li onclick="return addBuddySearch(\'' + encodeOnclick(destination) + '\', \'' + encodeOnclick(entered[b]) + '\');" data-xid="' + encodeQuotes(entered[b]) + '">' + current + '</li>';
}
// Finish the code generation
code += '</ul>';
// Creates the code in the DOM
$(destination).append(code);
// Put the hover on the first element
$(destination + ' ul li:first').addClass('hovered');
// Hover event, to not to remove this onblur and loose the click event
$(destination + ' ul li').hover(function() {
$(destination + ' ul li').removeClass('hovered');
$(this).addClass('hovered');
// Add a marker for the blur event
$(destination + ' ul').attr('mouse-hover', 'true');
}, function() {
$(this).removeClass('hovered');
// Remove the mouse over marker
$(destination + ' ul').removeAttr('mouse-hover');
});
}
}
// Handles the keyboard arrows press when searching
function arrowsBuddySearch(e, destination) {
// Down arrow: 40
// Up arrown: 38
// Initialize
var code = e.keyCode;
// Not the key we want here
if((code != 40) && (code != 38))
return;
// Remove the eventual mouse hover marker
$(destination + ' ul').removeAttr('mouse-hover');
// Create the path & get its size
var path = destination + ' ul li';
var pSize = $(path).size();
// Define the i value
var i = 0;
// Switching yet launched
if(exists(path + '.hovered')) {
var index = $(path).attr('data-hovered');
if(index)
i = parseInt(index);
if(code == 40)
i++;
else
i--;
}
else if(code == 38)
i = pSize - 1;
// We must not override the maximum i limit
if(i >= pSize)
i = 0;
// We must not have negative i
else if(i < 0)
i = pSize - 1;
// Modify the list
$(path + '.hovered').removeClass('hovered');
$(path).eq(i).addClass('hovered');
// Store the i index
$(path).attr('data-hovered', i);
return false;
}
// Filters the buddies in the roster
var SEARCH_FILTERED = false;
function goFilterBuddySearch(vFilter) {
// Put a marker
SEARCH_FILTERED = true;
// Show the buddies that match the search string
var rFilter = processBuddySearch(vFilter);
// Hide all the buddies
$('#buddy-list .buddy').hide();
// Only show the buddies which match the search
if(!BLIST_ALL) {
for(i in rFilter)
$('#buddy-list .buddy[data-xid="' + escape(rFilter[i]) + '"]:not(.hidden-buddy)').show();
} else {
for(i in rFilter)
$('#buddy-list .buddy[data-xid="' + escape(rFilter[i]) + '"]').show();
}
}
// Resets the buddy filtering in the roster
function resetFilterBuddySearch() {
// Remove the marker
SEARCH_FILTERED = false;
// Show all the buddies
$('#buddy-list .buddy').show();
// Only show available buddies
if(!BLIST_ALL)
$('#buddy-list .buddy.hidden-buddy').hide();
// Update the groups
updateGroups();
}
// Funnels the buddy filtering
function funnelFilterBuddySearch(keycode) {
// Get the input value
var input = $('#buddy-list .filter input');
var cancel = $('#buddy-list .filter a');
var value = input.val();
// Security: reset all the groups, empty or not, deployed or not
$('#buddy-list .one-group, #buddy-list .group-buddies').show();
$('#buddy-list .group span').text('-');
// Nothing is entered, or escape pressed
if(!value || (keycode == 27)) {
if(keycode == 27)
input.val('');
resetFilterBuddySearch();
cancel.hide();
}
// Process the filtering
else {
cancel.show();
goFilterBuddySearch(value);
}
// Update the groups
updateGroups();
}
// Searches for the nearest element (with a lower stamp than the current one)
function sortElementByStamp(stamp, element) {
var array = new Array();
var i = 0;
var nearest = 0;
// Add the stamp values to the array
$(element).each(function() {
var current_stamp = parseInt($(this).attr('data-stamp'));
// Push it!
array.push(current_stamp);
});
// Sort the array
array.sort();
// Get the nearest stamp value
while(stamp > array[i]) {
nearest = array[i];
i++;
}
return nearest;
}
/*
Jappix - An open social platform
These are the autocompletion tools JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 12/11/10
*/
// Sort an array with insensitivity to the case
function caseInsensitiveSort(a, b) {
// Put the two strings into lower case
a = a.toLowerCase();
b = b.toLowerCase();
// Process the sort
if(a > b)
return 1;
if(a < b)
return -1;
}
// Creates an array with the autocompletion results
function processAutocompletion(query, id) {
// Replace forbidden characters in regex
query = escapeRegex(query);
// Create an empty array
var results = new Array();
// Search in the roster
$('#' + id + ' .user').each(function() {
var nick = $(this).find('.name').text();
var regex = new RegExp('(^)' + query, 'gi');
if(nick.match(regex))
results.push(nick);
});
// Sort the array
results = results.sort(caseInsensitiveSort);
// Return the results array
return results;
}
// Resets the autocompletion tools
function resetAutocompletion(hash) {
$('#' + hash + ' .message-area').removeAttr('data-autocompletion-pointer').removeAttr('data-autocompletion-query');
}
// Autocompletes the chat input nick
function createAutocompletion(hash) {
// Initialize
var vSelector = $('#' + hash + ' .message-area');
var value = vSelector.val();
if(!value)
resetAutocompletion(hash);
var query = vSelector.attr('data-autocompletion-query');
// The autocompletion has not been yet launched
if(query == undefined) {
query = value;
vSelector.attr('data-autocompletion-query', query);
}
// Get the pointer
var pointer = vSelector.attr('data-autocompletion-pointer');
var i = 0;
if(pointer)
i = parseInt(pointer);
// We get the nickname
var nick = processAutocompletion(query, hash)[i];
// Shit, this is my nick!
if((nick != undefined) && (nick.toLowerCase() == getMUCNick(hash).toLowerCase())) {
// Increment
i++;
// Get the next nick
nick = processAutocompletion(query, hash)[i];
}
// We quote the nick
if(nick != undefined) {
// Increment
i++;
quoteMyNick(hash, nick);
// Put a pointer
vSelector.attr('data-autocompletion-pointer', i);
}
}
/*
Jappix - An open social platform
These are the welcome tool functions for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 16/01/12
*/
// Opens the welcome tools
function openWelcome() {
// Share message
var share_msg = printf(_e("Using Jappix, an open social platform. I am %s!"), getXID());
// Popup HTML content
var html =
'<div class="top">' + _e("Welcome!") + '</div>' +
'<div class="tab">' +
'<a href="#" class="tab-active" data-step="1">' + _e("Options") + '</a>' +
'<a href="#" class="tab-missing" data-step="2">' + _e("Friends") + '</a>' +
'<a href="#" class="tab-missing" data-step="3">' + _e("Share") + '</a>' +
'</div>' +
'<div class="content">' +
'<div class="lap-active one-lap welcome1">' +
'<div class="infos">' +
'<p class="infos-title">' + _e("Welcome on Jappix, your own social cloud!") + '</p>' +
'<p>' + _e("Before you start using it, you will have to change some settings, search for friends and complete your profile.") + '</p>' +
'</div>' +
'<a href="#" class="box enabled" title="' + _e("Click to disable") + '">' +
'<span class="option">' + _e("Sounds") + '</span>' +
'<span class="description">' + _e("Enable notification sounds") + '</span>' +
'<span class="image sound talk-images"></span>' +
'<span class="tick talk-images"></span>' +
'</a>' +
'<a href="#" class="box enabled pep-hidable" title="' + _e("Click to disable") + '">' +
'<span class="option">' + _e("Geolocation") + '</span>' +
'<span class="description">' + _e("Share your position on the globe") + '</span>' +
'<span class="image geolocation talk-images"></span>' +
'<span class="tick talk-images"></span>' +
'</a>' +
'<a href="#" class="box xmpplinks-hidable" title="' + _e("Click to enable") + '">' +
'<span class="option">' + _e("XMPP links") + '</span>' +
'<span class="description">' + _e("Open XMPP links with Jappix") + '</span>' +
'<span class="image xmpp talk-images"></span>' +
'<span class="tick talk-images"></span>' +
'</a>' +
'<a href="#" class="box mam-hidable pref" title="' + _e("Click to enable") + '">' +
'<span class="option">' + _e("Message archiving") + '</span>' +
'<span class="description">' + _e("Store a history of your chats") + '</span>' +
'<span class="image mam talk-images"></span>' +
'<span class="tick talk-images"></span>' +
'</a>' +
'<a href="#" class="box" title="' + _e("Click to enable") + '">' +
'<span class="option">' + _e("Offline friends") + '</span>' +
'<span class="description">' + _e("Don\'t hide offline friends") + '</span>' +
'<span class="image offline talk-images"></span>' +
'<span class="tick talk-images"></span>' +
'</a>' +
'</div>' +
'<div class="one-lap welcome2">' +
'<div class="infos">' +
'<p class="infos-title">' + _e("Friends") + '</p>' +
'<p>' + _e("Use this tool to find your friends on the server you are using right now, or add them later.") + '</p>' +
'</div>' +
'<div class="results welcome-results"></div>' +
'</div>' +
'<div class="one-lap welcome3">' +
'<div class="infos">' +
'<p class="infos-title">' + _e("Share") + '</p>' +
'<p>' + _e("Good job! Now, you can share Jappix with your friends!") + '</p>' +
'<p>' + _e("When you will press the save button, the profile editor will be opened. Happy socializing!") + '</p>' +
'</div>' +
'<a class="box share first" href="http://www.facebook.com/sharer/sharer.php?u=' + encodeQuotes(generateURL(JAPPIX_LOCATION)) + '" target="_blank">' +
'<span class="logo facebook welcome-images"></span>' +
'<span class="name">Facebook</span>' +
'<span class="description">' + printf(_e("Share Jappix on %s"), 'Facebook') + '</span>' +
'<span class="go talk-images"></span>' +
'</a>' +
'<a class="box share" href="http://twitter.com/intent/tweet?text=' + encodeQuotes(share_msg) + '&amp;url=' + encodeQuotes(generateURL(JAPPIX_LOCATION)) + '" target="_blank">' +
'<span class="logo twitter welcome-images"></span>' +
'<span class="name">Twitter</span>' +
'<span class="description">' + printf(_e("Share Jappix on %s"), 'Twitter') + '</span>' +
'<span class="go talk-images"></span>' +
'</a>' +
'<a class="box share" href="http://www.google.com/buzz/post?message=' + encodeQuotes(share_msg) + '&amp;url=' + encodeQuotes(generateURL(JAPPIX_LOCATION)) + '" target="_blank">' +
'<span class="logo buzz welcome-images"></span>' +
'<span class="name">Google Buzz</span>' +
'<span class="description">' + printf(_e("Share Jappix on %s"), 'Google Buzz') + '</span>' +
'<span class="go talk-images"></span>' +
'</a>' +
'<a class="box share" href="http://identi.ca/index.php?action=newnotice&amp;status_textarea=' + encodeQuotes(share_msg) + ' ' + encodeQuotes(generateURL(JAPPIX_LOCATION)) + '" target="_blank">' +
'<span class="logo identica welcome-images"></span>' +
'<span class="name">Identi.ca</span>' +
'<span class="description">' + printf(_e("Share Jappix on %s"), 'Identi.ca') + '</span>' +
'<span class="go talk-images"></span>' +
'</a>' +
'</div>' +
'</div>' +
'<div class="bottom">' +
'<div class="wait wait-medium"></div>' +
'<a href="#" class="finish next">' + _e("Next") + ' »</a>' +
'<a href="#" class="finish save">' + _e("Save") + '</a>' +
'</div>';
// Create the popup
createPopup('welcome', html);
// Unavoidable popup
$('#welcome').addClass('unavoidable');
// Apply the features
applyFeatures('welcome');
// Associate the events
launchWelcome();
logThis('Welcome assistant opened.');
}
// Closes the welcome tools
function closeWelcome() {
// Destroy the popup
destroyPopup('welcome');
return false;
}
// Switches the welcome tabs
function switchWelcome(id) {
// Path to
var welcome = '#welcome ';
var content = welcome + '.content .';
var tab = welcome + '.tab ';
var wait = $(welcome + '.wait');
$(content + 'one-lap').hide();
$(content + 'welcome' + id).show();
$(tab + 'a').removeClass('tab-active');
$(tab + 'a[data-step="' + id + '"]').addClass('tab-active').removeClass('tab-missing');
// Update the "save" button if all is okay
if(!exists(tab + '.tab-missing')) {
var finish = welcome + '.finish.';
$(finish + 'save').show();
$(finish + 'next').hide();
}
// If this is ID 2: vJUD search
if(id == 2) {
wait.show();
dataForm(HOST_VJUD, 'search', '', '', 'welcome');
}
else
wait.hide();
return false;
}
// Sends the welcome options
function sendWelcome(array) {
// Sends the options
var iq = new JSJaCIQ();
iq.setType('set');
var query = iq.setQuery(NS_PRIVATE);
var storage = query.appendChild(iq.buildNode('storage', {'xmlns': NS_OPTIONS}));
// Value array
var tags = new Array('sounds', 'geolocation', '', '', 'roster-showall');
// Build the XML with the array
for(i in array) {
var value = array[i];
var tag = tags[i];
if((i != 2) && (i != 3) && tag && value) {
storage.appendChild(iq.buildNode('option', {'type': tag, 'xmlns': NS_OPTIONS}, value));
setDB('options', tag, value);
}
}
con.send(iq);
// If geolocation is enabled
if(array[1] == '1')
geolocate();
}
// Saves the welcome options
var END_WELCOME = false;
function saveWelcome() {
// Get the new options
var array = new Array();
$('#welcome a.box').each(function() {
var current = '0';
if($(this).hasClass('enabled'))
current = '1';
array.push(current);
});
// If XMPP links is enabled
if(array[2] == '1')
xmppLinksHandler();
// If offline buddies showing is enabled
if(array[4] == '1')
showAllBuddies('welcome');
// If archiving is supported by the server
if(enabledMAM()) {
// If archiving is enabled
if(array[3] == '1') {
setConfigMAM('roster');
}
}
// Send the new options
sendWelcome(array);
// Close the welcome tool
closeWelcome();
// Open the profile editor
openVCard();
// Unavoidable popup
$('#vcard').addClass('unavoidable');
END_WELCOME = true;
return false;
}
// Goes to the next welcome step
function nextWelcome() {
// Check the next step to go to
var next = 1;
var missing = '#welcome .tab a.tab-missing';
if(exists(missing))
next = parseInt($(missing + ':first').attr('data-step'));
// Switch to the next step
switchWelcome(next);
return false;
}
// Plugin launcher
function launchWelcome() {
// Click events
$('#welcome .tab a').click(function() {
// Switch to the good tab
var key = parseInt($(this).attr('data-step'));
return switchWelcome(key);
});
$('#welcome a.box:not(.share)').click(function() {
if($(this).hasClass('enabled'))
$(this).removeClass('enabled').attr('title', _e("Click to enable"));
else
$(this).addClass('enabled').attr('title', _e("Click to disable"));
return false;
});
$('#welcome .bottom .finish').click(function() {
if($(this).is('.next'))
return nextWelcome();
if($(this).is('.save'))
return saveWelcome();
return false;
});
}
/*
Jappix - An open social platform
These are the Jappix Me tool functions for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 21/06/12
*/
// Opens the Me tools
function openMe() {
// Popup HTML content
var html =
'<div class="top">' + _e("Public profile") + '</div>' +
'<div class="content">' +
'<a class="me-images logo" href="https://me.jappix.com/" target="_blank"></a>' +
'<div class="infos">' +
'<p class="infos-title">' + _e("Your profile anywhere on the Web.") + '</p>' +
'<p>' + printf(_e("%s is a Jappix.com service which makes your XMPP profile public. It is easier to share it. No XMPP account is required to view your social channel, your current position and your contact details."), '<a href="https://me.jappix.com/" target="_blank">Jappix Me</a>') + '</p>' +
'<p>' + _e("Furthermore, every picture you post in your social channel is added to a beautiful picture timeline. You can now view the pictures you shared year by year.") + '</p>' +
'<p>' + _e("You can also use your XMPP avatar as a single avatar for every website, blog and forum you use. When you change it on XMPP, the new avatar appears everywhere. What a genius improvement!") + '</p>' +
'</div>' +
'<a class="go one-button" href="https://me.jappix.com/new" target="_blank">' + _e("Yay, let's create your public profile!") + '</a>' +
'</div>' +
'<div class="bottom">' +
'<a href="#" class="finish">' + _e("Close") + '</a>' +
'</div>';
// Create the popup
createPopup('me', html);
// Associate the events
launchMe();
logThis('Public profile tool opened.');
}
// Closes the Me tools
function closeMe() {
// Destroy the popup
destroyPopup('me');
// We finished
END_WELCOME = false;
return false;
}
// Plugin launcher
function launchMe() {
// Click events
$('#me .content a.go').click(function() {
closeMe();
});
$('#me .bottom .finish').click(closeMe);
}
/*
Jappix - An open social platform
These are the Roster Item Exchange JS script for Jappix
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 20/06/12
*/
// Opens the rosterx tools
function openRosterX(data) {
// Popup HTML content
var html =
'<div class="top">' + _e("Suggested friends") + '</div>' +
'<div class="content">' +
'<div class="rosterx-head">' +
'<a href="#" class="uncheck">' + _e("Uncheck all") + '</a>' +
'<a href="#" class="check">' + _e("Check all") + '</a>' +
'</div>' +
'<div class="results"></div>' +
'</div>' +
'<div class="bottom">' +
'<a href="#" class="finish save">' + _e("Save") + '</a>' +
'<a href="#" class="finish cancel">' + _e("Cancel") + '</a>' +
'</div>';
// Create the popup
createPopup('rosterx', html);
// Associate the events
launchRosterX();
// Parse the data
parseRosterX(data);
logThis('Roster Item Exchange popup opened.');
}
// Closes the rosterx tools
function closeRosterX() {
// Destroy the popup
destroyPopup('rosterx');
return false;
}
// Parses a rosterx query
function parseRosterX(data) {
// Main selector
var x = $(data).find('x[xmlns="' + NS_ROSTERX + '"]:first');
// Parse data
x.find('item').each(function() {
// Generate group XML
var group = '';
$(this).find('group').each(function() {
group += '<group>' + $(this).text().htmlEnc() + '</group>';
});
if(group)
group = '<groups>' + group + '</groups>';
// Display it!
displayRosterX($(this).attr('jid'), $(this).attr('name'), group, $(this).attr('action'));
});
// Click to check/uncheck
$('#rosterx .oneresult').click(function(evt) {
// No need to apply when click on input
if($(evt.target).is('input[type="checkbox"]'))
return;
// Input selector
var checkbox = $(this).find('input[type="checkbox"]');
// Check or uncheck?
if(checkbox.filter(':checked').size())
checkbox.removeAttr('checked');
else
checkbox.attr('checked', true);
});
}
// Displays a rosterx item
function displayRosterX(xid, nick, group, action) {
// End if no XID
if(!xid)
return false;
// Set up a default action if no one
if(!action || (action != 'modify') || (action != 'delete'))
action = 'add';
// Override "undefined" for nickname
if(!nick)
nick = '';
// Display it
$('#rosterx .results').append(
'<div class="oneresult">' +
'<input type="checkbox" checked="" data-name="' + encodeQuotes(nick) + '" data-xid="' + encodeQuotes(xid) + '" data-action="' + encodeQuotes(action) + '" data-group="' + encodeQuotes(group) + '" />' +
'<span class="name">' + nick.htmlEnc() + '</span>' +
'<span class="xid">' + xid.htmlEnc() + '</span>' +
'<span class="action ' + action + ' talk-images"></span>' +
'</div>'
);
}
// Saves the rosterx settings
function saveRosterX() {
// Send the requests
$('#rosterx .results input[type="checkbox"]').filter(':checked').each(function() {
// Read the attributes
var nick = $(this).attr('data-name');
var xid = $(this).attr('data-xid');
var action = $(this).attr('data-action');
var group = $(this).attr('data-group');
// Parse groups XML
if(group) {
var group_arr = []
$(group).find('group').each(function() {
group_arr.push($(this).text().revertHtmlEnc());
});
}
// Process the asked action
var roster_item = $('#buddy-list .' + hex_md5(xid));
switch(action) {
// Buddy add
case 'add':
if(!exists(roster_item)) {
sendSubscribe(xid, 'subscribe');
sendRoster(xid, '', nick, group_arr);
}
break;
// Buddy edit
case 'modify':
if(exists(roster_item))
sendRoster(xid, '', nick, group_arr);
break;
// Buddy delete
case 'delete':
if(exists(roster_item))
sendRoster(xid, 'remove');
break;
}
});
// Close the popup
closeRosterX();
}
// Plugin launcher
function launchRosterX() {
// Click events
$('#rosterx .bottom .finish').click(function() {
if($(this).is('.save'))
return saveRosterX();
if($(this).is('.cancel'))
return closeRosterX();
});
$('#rosterx .rosterx-head a').click(function() {
if($(this).is('.check'))
$('#rosterx .results input[type="checkbox"]').attr('checked', true);
else if($(this).is('.uncheck'))
$('#rosterx .results input[type="checkbox"]').removeAttr('checked');
return false;
});
}
/*
Jappix - An open social platform
Implementation of XEP-0313: Message Archive Management
-------------------------------------------------
License: AGPL
Author: Valérian Saliou
Last revision: 04/08/13
*/
/* -- MAM Constants -- */
// Note: Internet Explorer does not support 'const'
// We use vars as a fix...
var MAM_REQ_MAX = 25;
var MAM_PREF_DEFAULTS = {
'always' : 1,
'never' : 1,
'roster' : 1
};
/* -- MAM Variables -- */
var MAM_MAP_REQS = {};
var MAM_MAP_PENDING = {};
var MAM_MAP_STATES = {};
var MAM_MSG_QUEUE = {};
/* -- MAM Configuration -- */
// Gets the MAM configuration
function getConfigMAM() {
try {
// Lock the archiving options
$('#archiving').attr('disabled', true);
// Get the archiving configuration
var iq = new JSJaCIQ();
iq.setType('get');
iq.appendNode('prefs', { 'xmlns': NS_URN_MAM });
con.send(iq, handleConfigMAM);
} catch(e) {
logThis('getConfigMAM > ' + e, 1);
}
}
// Handles the MAM configuration
function handleConfigMAM(iq) {
try {
if(iq.getType() != 'error') {
// Read packet
var cur_default = $(iq.getNode()).find('prefs[xmlns="' + NS_URN_MAM + '"]').attr('default') || 'never';
if(!(cur_default in MAM_PREF_DEFAULTS)) {
cur_default = 'never';
}
// Apply value to options
$('#archiving').val(cur_default);
}
// Unlock the archiving option
$('#archiving').removeAttr('disabled');
// All done.
waitOptions('mam');
} catch(e) {
logThis('handleConfigMAM > ' + e, 1);
}
}
// Sets the MAM configuration
function setConfigMAM(pref_default) {
try {
// Check parameters
if(!(pref_default in MAM_PREF_DEFAULTS)) {
pref_default = 'never';
}
// Send new configuration
var iq = new JSJaCIQ();
iq.setType('set');
iq.appendNode('prefs', { 'xmlns': NS_URN_MAM, 'default': pref_default });
con.send(iq);
} catch(e) {
logThis('setConfigMAM > ' + e, 1);
}
}
/* -- MAM Purge -- */
// Removes all (or given) MAM archives
function purgeArchivesMAM(args) {
try {
if(typeof args != 'object') {
args = {};
}
var iq = new JSJaCIQ();
iq.setType('set');
var purge = iq.appendNode('purge', { 'xmlns': NS_URN_MAM });
for(c in args) {
if(args[c]) purge.appendChild(iq.buildNode(c, {'xmlns': NS_URN_MAM}, args[c]));
}
con.send(iq, function(iq) {
if(iq.getType() == 'result') {
logThis('Archives purged (MAM).', 3);
} else {
logThis('Error purging archives (MAM).', 1);
}
});
} catch(e) {
logThis('purgeArchivesMAM > ' + e, 1);
}
}
/* -- MAM Retrieval -- */
// Gets the MAM configuration
function getArchivesMAM(args, rsm_args, callback) {
try {
if(typeof args != 'object') {
args = {};
}
var req_id = genID();
if(args['with']) {
MAM_MAP_PENDING[args['with']] = 1;
MAM_MAP_REQS[req_id] = args['with'];
}
var iq = new JSJaCIQ();
iq.setType('get');
iq.setID(req_id);
var query = iq.setQuery(NS_URN_MAM);
for(c in args) {
if(args[c] != null) query.appendChild(iq.buildNode(c, {'xmlns': NS_URN_MAM}, args[c]));
}
if(rsm_args && typeof rsm_args == 'object') {
var rsm_set = query.appendChild(iq.buildNode('set', {'xmlns': NS_RSM}));
for(r in rsm_args) {
if(rsm_args[r] != null) rsm_set.appendChild(iq.buildNode(r, {'xmlns': NS_RSM}, rsm_args[r]));
}
}
con.send(iq, function(res_iq) {
handleArchivesMAM(res_iq, callback);
});
} catch(e) {
logThis('getArchivesMAM > ' + e, 1);
}
}
// Handles the MAM configuration
function handleArchivesMAM(iq, callback) {
try {
var res_id = iq.getID();
var res_with;
if(res_id && res_id in MAM_MAP_REQS) {
res_with = MAM_MAP_REQS[res_id];
}
if(iq.getType() != 'error') {
if(res_with) {
var res_sel = $(iq.getQuery());
var res_rsm_sel = res_sel.find('set[xmlns="' + NS_RSM + '"]');
// Store that data
MAM_MAP_STATES[res_with] = {
'date': {
'start': res_sel.find('start').eq(0).text(),
'end': res_sel.find('end').eq(0).text()
},
'rsm': {
'first': res_rsm_sel.find('first').eq(0).text(),
'last': res_rsm_sel.find('last').eq(0).text(),
'count': parseInt(res_rsm_sel.find('count').eq(0).text() || 0)
}
}
// Generate stamps for easy operations
var start_stamp = extractStamp(Date.jab2date(MAM_MAP_STATES[res_with]['date']['start']));
var start_end = extractStamp(Date.jab2date(MAM_MAP_STATES[res_with]['date']['end']));
// Create MAM messages target
var target_html = '<div class="mam-chunk" data-start="' + encodeQuotes(start_stamp) + '" data-end="' + encodeQuotes(start_end) + '"></div>';
var target_content_sel = $('#' + hex_md5(res_with) + ' .content');
var target_wait_sel = target_content_sel.find('.wait-mam');
if(target_wait_sel.size()) {
target_wait_sel.after(target_html);
} else {
target_content_sel.prepend(target_html);
}
// Any enqueued message to display?
if(typeof MAM_MSG_QUEUE[res_with] == 'object') {
for(i in MAM_MSG_QUEUE[res_with]) {
(MAM_MSG_QUEUE[res_with][i])();
}
delete MAM_MSG_QUEUE[res_with];
}
// Remove XID from pending list
if(res_with in MAM_MAP_PENDING) {
delete MAM_MAP_PENDING[res_with];
}
logThis('Got archives from: ' + res_with, 3);
} else {
logThis('Could not associate archive response with a known JID.', 2);
}
} else {
logThis('Error handing archives (MAM).', 1);
}
// Execute callback?
if(typeof callback == 'function') {
callback(iq);
}
} catch(e) {
logThis('handleArchivesMAM > ' + e, 1);
}
}
// Handles a MAM-forwarded message stanza
function handleMessageMAM(fwd_stanza, c_delay) {
try {
// Build message node
var c_message = fwd_stanza.find('message');
if(c_message[0]) {
// Re-build a proper JSJaC message stanza
var message = JSJaCPacket.wrapNode(c_message[0]);
// Check message type
var type = message.getType() || 'chat';
if(type == 'chat') {
// Read message data
var xid = bareXID(getStanzaFrom(message));
var id = message.getID();
var from_xid = xid;
var b_name = getBuddyName(xid);
var mode = (xid == getXID()) ? 'me': 'him';
// Refactor chat XID (in case we were the sender of the archived message)
if(mode == 'me') {
xid = bareXID(message.getTo())
}
var hash = hex_md5(xid);
var body = message.getBody();
// Read delay (required since we deal w/ a past message!)
var time, stamp;
var delay = c_delay.attr('stamp');
if(delay) {
time = relativeDate(delay);
stamp = extractStamp(Date.jab2date(delay));
}
// Last-minute checks before display
if(time && stamp && body) {
var mam_chunk_path = '#' + hash + ' .mam-chunk';
// No chat auto-scroll?
var no_scroll = exists(mam_chunk_path);
// Select the custom target
var c_target_sel = function() {
return $(mam_chunk_path).filter(function() {
return $(this).attr('data-start') <= stamp && $(this).attr('data-end') >= stamp
}).filter(':first');
};
// Display the message in that target
var c_msg_display = function() {
displayMessage(type, from_xid, hash, b_name.htmlEnc(), body, time, stamp, 'old-message', true, null, mode, null, c_target_sel(), no_scroll);
};
// Hack: do not display the message in case we would duplicate it w/ current session messages
// only used when initiating a new chat, avoids collisions
if(!(xid in MAM_MAP_STATES) && $('#' + hash).find('.one-line.user-message:last').text() == body) {
return;
}
if(c_target_sel().size()) {
// Display the message in that target
c_msg_display();
} else {
// Delay display (we may not have received the MAM reply ATM)
if(typeof MAM_MSG_QUEUE[xid] != 'object') {
MAM_MSG_QUEUE[xid] = [];
}
MAM_MSG_QUEUE[xid].push(c_msg_display);
}
}
}
}
} catch(e) {
logThis('handleMessageMAM > ' + e, 1);
}
}