Commit 52207f72 authored by Dele Olajide's avatar Dele Olajide Committed by dele

JitsiVideobridge - Added Fastpath video using candy (agent) and ofmeet (user)

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13858 b35dd754-fafc-0310-a699-88a17e54d16e
parent 786247b5
Credits
=======
- [famfamfam silk icons](http://www.famfamfam.com/lab/icons/silk/) is a smooth, free icon set, containing over 700 16-by-16 pixel icons.
- [Simple Smileys](http://simplesmileys.org) are beautifully simple emoticons.
- [Flash MP3 Player](http://flash-mp3-player.net/players/js) is a very simple flash audio player used by Candy for audio notifications.
- [Colin Snover](http://zetafleet.com/blog/javascript-dateparse-for-iso-8601) provides a fix for browsers not supporting latest Date.parse().
- [Ben Cherry](http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth) wrote a great article about the JS module pattern.
- [Amiado Group](http://www.amiadogroup.com) allowed us to make Candy freely available for everyone! :)
\ No newline at end of file
Copyright (c) 2011 Amiado Group AG
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.
#
# Makefile for Candy
# Candy - Chats are not dead yet
#
# Copyright:
# (c) 2011 Amiado Group AG
#
# Authors:
# - Patrick Stadler <patrick.stadler@gmail.com>
# - Michael Weibel <michael.weibel@gmail.com>
#
SHELL=/bin/bash
DOC_DIR = docs
NDPROJ_DIR = .ndproj
SRC_DIR = src
LIBS_DIR = libs
CANDY_BUNDLE = candy.bundle.js
CANDY_BUNDLE_MIN = candy.min.js
CANDY_BUNDLE_LIBRARIES = libs/libs.bundle.js
CANDY_BUNDLE_LIBRARIES_MIN = libs/libs.min.js
CANDY_FILES = $(SRC_DIR)/candy.js $(SRC_DIR)/core.js $(SRC_DIR)/view.js $(SRC_DIR)/util.js $(SRC_DIR)/core/action.js $(SRC_DIR)/core/chatRoom.js $(SRC_DIR)/core/chatRoster.js $(SRC_DIR)/core/chatUser.js $(SRC_DIR)/core/event.js $(SRC_DIR)/view/event.js $(SRC_DIR)/view/observer.js $(SRC_DIR)/view/pane.js $(SRC_DIR)/view/template.js $(SRC_DIR)/view/translation.js
CANDY_LIBS_FILES = $(LIBS_DIR)/strophejs/strophe.js $(LIBS_DIR)/strophejs-plugins/muc/strophe.muc.js $(LIBS_DIR)/mustache.js/mustache.js $(LIBS_DIR)/jquery-i18n/jquery.i18n.js $(LIBS_DIR)/dateformat/dateFormat.js
CANDY_FILES_BUNDLE = $(CANDY_FILES:.js=.bundle)
CANDY_LIBS_FILES_BUNDLE = $(CANDY_LIBS_FILES:.js=.libs-bundle)
all: bundle min
bundle: clean-bundle $(CANDY_FILES_BUNDLE)
%.bundle: %.js
@@echo -n "Bundling" $< "..."
@@cat $< >> $(CANDY_BUNDLE)
@@echo "done"
min: $(CANDY_BUNDLE)
@@echo -n "Compressing" $(CANDY_BUNDLE) "..."
ifdef YUI_COMPRESSOR
@@java -jar $(YUI_COMPRESSOR) --type js $(CANDY_BUNDLE) -o $(CANDY_BUNDLE_MIN) --charset utf-8
@@echo "done ("$(CANDY_BUNDLE_MIN)")"
else
@@echo "aborted"
@@echo "** You can safely use the uncompressed bundle ("$(CANDY_BUNDLE)")"
@@echo "** YUI Compressor is required to build the minified version."
@@echo "** Please set YUI_COMPRESSOR to the path to the jar file."
endif
libs: libs-bundle libs-min
libs-bundle: clean-libs $(CANDY_LIBS_FILES_BUNDLE)
%.libs-bundle: %.js
@@echo -n "Bundling" $< "..."
@@cat $< >> $(CANDY_BUNDLE_LIBRARIES)
@@echo "done"
libs-min: $(CANDY_BUNDLE_LIBRARIES)
@@echo -n "Compressing" $(CANDY_BUNDLE_LIBRARIES) "..."
ifdef YUI_COMPRESSOR
@@java -jar $(YUI_COMPRESSOR) --type js $(CANDY_BUNDLE_LIBRARIES) -o $(CANDY_BUNDLE_LIBRARIES_MIN) --charset utf-8
@@echo "done ("$(CANDY_BUNDLE_LIBRARIES_MIN)")"
else
@@echo "aborted"
@@echo "** You can safely use the uncompressed bundle ("$(CANDY_BUNDLE_LIBRARIES)")"
@@echo "** YUI Compressor is required to build the minified version."
@@echo "** Please set YUI_COMPRESSOR to the path to the jar file."
endif
docs:
@@echo "Building candy documentation ..."
ifdef NATURALDOCS_DIR
@@if [ ! -d $(NDPROJ_DIR) ]; then mkdir $(NDPROJ_DIR); fi
@@if [ ! -d $(DOC_DIR) ]; then mkdir $(DOC_DIR); fi
@@$(NATURALDOCS_DIR)/NaturalDocs -q --exclude-source libs --exclude-source res --exclude-source candy.min.js --exclude-source candy.bundle.js -i . -o html $(DOC_DIR) -p $(NDPROJ_DIR)
@@rm -r $(NDPROJ_DIR)
@@echo "Documentation built."
@@echo
else
@@echo "aborted"
@@echo "** NaturalDocs is required to build the documentation."
@@echo "** Please set NATURALDOCS_DIR to the path to the NaturalDocs executable"
endif
clean: clean-bundle clean-libs
clean-bundle:
@@echo -n "Cleaning bundles ..."
@@rm -f $(CANDY_BUNDLE) $(CANDY_BUNDLE_MIN)
@@echo "done"
clean-libs:
@@echo -n "Cleaning library bundles ..."
@@rm -f $(CANDY_BUNDLE_LIBRARIES) $(CANDY_BUNDLE_LIBRARIES_MIN)
@@echo "done"
clean-docs:
@@echo -n "Cleaning documentation ..."
@@rm -rf $(NDPROJ_DIR) $(DOC_DIR)
@@echo "done"
.PHONY: all docs clean libs
Candy — a JavaScript-based multi-user chat client
==================================================
Visit the official project page: http://candy-chat.github.com/candy
Features
--------
- Focused on real-time multi-user chatting
- Easy to configure, easy to run, easy to use
- Highly customizable
- 100% well-documented JavaScript source code
- Built for Jabber (XMPP), using famous technologies
- Used and approved in a productive environment with up to 400 concurrent users
- Works with all major web browsers including IE7
Plugins
-------
If you wish to add new functionality (to your candy installation) or contribute plugins, take a look at our [plugin repository](http://github.com/candy-chat/candy-plugins).
Support & Community
-------------------
Take a look at our [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions). If it doesn't solve your questions, you're welcome to join our [Mailinglist on Google Groups](http://groups.google.com/group/candy-chat).
You don't need to have a Gmail account for it.
[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/a41a8075608abeaf99db685d7ef29cf6 "githalytics.com")](http://githalytics.com/candy-chat/candy)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Candy - Chats are not dead yet</title>
<link rel="shortcut icon" href="../res/img/favicon.png" type="image/gif" />
<link rel="stylesheet" type="text/css" href="../res/default.css" />
<link rel="stylesheet" type="text/css" href="../plugins/colors/candy.css" />
<link rel="stylesheet" type="text/css" href="../plugins/roomPanel/default.css" />
<link rel="stylesheet" type="text/css" href="../plugins/timeago/candy.css" />
<link rel="stylesheet" type="text/css" href="../plugins/videobridge/candy.css" />
<script type="text/javascript" src="../libs/jquery.min.js"></script>
<script type="text/javascript" src="../libs/libs.min.js"></script>
<script type="text/javascript" src="../libs/strophe-connection-websockets.js"></script>
<script type="text/javascript" src="../libs/candy.bundle.js"></script>
<script type="text/javascript" src="../plugins/colors/candy.js"></script>
<script type="text/javascript" src="../plugins/roomPanel/roomPanel.js"></script>
<script type="text/javascript" src="../plugins/timeago/candy.js"></script>
<script type="text/javascript" src="../plugins/videobridge/candy.js"></script>
<script type="text/javascript" src="../plugins/videobridge/jingle.sdp.js"></script>
<script type="text/javascript" src="../plugins/fastpath/candy.js"></script>
<script type="text/javascript">
function urlParam(name)
{
var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (!results) { return undefined; }
return results[1] || undefined;
};
$(document).ready(function() {
Candy.init('/http-bind/', {
core: { debug: false, websockets: true},
view: { resources: '../res/' }
});
var username = urlParam("username");
var password = urlParam("password");
CandyShop.RoomPanel.init({
// domain that hosts the muc rooms, only required if autoDetectRooms is enabled
mucDomain: 'conference.' + window.location.hostname,
// show room list if all rooms are closed, default value is true. [optional]
showIfAllTabClosed: true,
// detect rooms before showing list, default value is true. [optional]
autoDetectRooms: true,
// how long in seconds before refreshing room list, default value is 600. [optional]
roomCacheTime: 600
});
CandyShop.Colors.init();
CandyShop.Timeago.init();
CandyShop.Videobridge.init();
CandyShop.Fastpath.init();
Candy.Core.connect();
// for testing only
// Candy.Core.connect(username + "@" + window.location.hostname, password, username);
});
</script>
</head>
<body>
<div class="fade_line"></div>
<div id="candy"></div>
<div class="fade_line"></div>
<video id="largeVideo" autoplay oncontextmenu="return false;"></video>
<div id="remoteVideos">
<video id="localVideo" autoplay oncontextmenu="return false;" muted/>
</div>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* Date Format 1.2.3
* (c) 2007-2009 Steven Levithan <stevenlevithan.com>
* MIT license
*
* Includes enhancements by Scott Trenda <scott.trenda.net>
* and Kris Kowal <cixar.com/~kris.kowal/>
*
* Accepts a date, a mask, or a date and a mask.
* Returns a formatted version of the given date.
* The date defaults to the current date/time.
* The mask defaults to dateFormat.masks.default.
*
* @link http://blog.stevenlevithan.com/archives/date-time-format
*/
var dateFormat = function () {
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
timezoneClip = /[^-+\dA-Z]/g,
pad = function (val, len) {
val = String(val);
len = len || 2;
while (val.length < len) val = "0" + val;
return val;
};
// Regexes and supporting functions are cached through closure
return function (date, mask, utc) {
var dF = dateFormat;
// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
mask = date;
date = undefined;
}
// Passing date through Date applies Date.parse, if necessary
date = date ? new Date(date) : new Date;
if (isNaN(date)) throw SyntaxError("invalid date");
mask = String(dF.masks[mask] || mask || dF.masks["default"]);
// Allow setting the utc argument via the mask
if (mask.slice(0, 4) == "UTC:") {
mask = mask.slice(4);
utc = true;
}
var _ = utc ? "getUTC" : "get",
d = date[_ + "Date"](),
D = date[_ + "Day"](),
m = date[_ + "Month"](),
y = date[_ + "FullYear"](),
H = date[_ + "Hours"](),
M = date[_ + "Minutes"](),
s = date[_ + "Seconds"](),
L = date[_ + "Milliseconds"](),
o = utc ? 0 : date.getTimezoneOffset(),
flags = {
d: d,
dd: pad(d),
ddd: dF.i18n.dayNames[D],
dddd: dF.i18n.dayNames[D + 7],
m: m + 1,
mm: pad(m + 1),
mmm: dF.i18n.monthNames[m],
mmmm: dF.i18n.monthNames[m + 12],
yy: String(y).slice(2),
yyyy: y,
h: H % 12 || 12,
hh: pad(H % 12 || 12),
H: H,
HH: pad(H),
M: M,
MM: pad(M),
s: s,
ss: pad(s),
l: pad(L, 3),
L: pad(L > 99 ? Math.round(L / 10) : L),
t: H < 12 ? "a" : "p",
tt: H < 12 ? "am" : "pm",
T: H < 12 ? "A" : "P",
TT: H < 12 ? "AM" : "PM",
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
};
return mask.replace(token, function ($0) {
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
});
};
}();
// Some common format strings
dateFormat.masks = {
"default": "ddd mmm dd yyyy HH:MM:ss",
shortDate: "m/d/yy",
mediumDate: "mmm d, yyyy",
longDate: "mmmm d, yyyy",
fullDate: "dddd, mmmm d, yyyy",
shortTime: "h:MM TT",
mediumTime: "h:MM:ss TT",
longTime: "h:MM:ss TT Z",
isoDate: "yyyy-mm-dd",
isoTime: "HH:MM:ss",
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};
// Internationalization strings
dateFormat.i18n = {
dayNames: [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
]
};
// For convenience...
Date.prototype.format = function (mask, utc) {
return dateFormat(this, mask, utc);
};
\ No newline at end of file
/*! jQuery v1.7 jquery.com | jquery.org/license */
(function(a,b){function cA(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cx(a){if(!cm[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cn||(cn=c.createElement("iframe"),cn.frameBorder=cn.width=cn.height=0),b.appendChild(cn);if(!co||!cn.createElement)co=(cn.contentWindow||cn.contentDocument).document,co.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),co.close();d=co.createElement(a),co.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cn)}cm[a]=e}return cm[a]}function cw(a,b){var c={};f.each(cs.concat.apply([],cs.slice(0,b)),function(){c[this]=a});return c}function cv(){ct=b}function cu(){setTimeout(cv,0);return ct=f.now()}function cl(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ck(){try{return new a.XMLHttpRequest}catch(b){}}function ce(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cd(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function cc(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bG.test(a)?d(a,e):cc(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)cc(a+"["+e+"]",b[e],c,d);else d(a,b)}function cb(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function ca(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bV,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=ca(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=ca(a,c,d,e,"*",g));return l}function b_(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bR),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bE(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bz:bA;if(d>0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bB(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function br(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bi,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bq(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bp(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bp)}function bp(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bo(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bn(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bm(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bl(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a){var b=Y.split(" "),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(){return!0}function M(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.add(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;B.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return a!=null&&m.test(a)&&!isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:H?function(a){return a==null?"":H.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?F.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(I)return I.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=G.call(arguments,2),g=function(){return a.apply(c,f.concat(G.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){J["[object "+b+"]"]=b.toLowerCase()}),A=e.uaMatch(z),A.browser&&(e.browser[A.browser]=!0,e.browser.version=A.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?C=function(){c.removeEventListener("DOMContentLoaded",C,!1),e.ready()}:c.attachEvent&&(C=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",C),e.ready())}),typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return e});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){return i.done.apply(i,arguments).fail.apply(i,arguments)},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/><nav></nav>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,unknownElems:!!a.getElementsByTagName("nav").length,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",enctype:!!c.createElement("form").enctype,submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.lastChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-999px",top:"-999px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;f(function(){var a,b,d,e,g,h,i=1,j="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",l="visibility:hidden;border:0;",n="style='"+j+"border:5px solid #000;padding:0;'",p="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>";m=c.getElementsByTagName("body")[0];!m||(a=c.createElement("div"),a.style.cssText=l+"width:0;height:0;position:static;top:0;margin-top:"+i+"px",m.insertBefore(a,m.firstChild),o=c.createElement("div"),o.style.cssText=j+l,o.innerHTML=p,a.appendChild(o),b=o.firstChild,d=b.firstChild,g=b.nextSibling.firstChild.firstChild,h={doesNotAddBorder:d.offsetTop!==5,doesAddBorderForTableAndCells:g.offsetTop===5},d.style.position="fixed",d.style.top="20px",h.fixedPosition=d.offsetTop===20||d.offsetTop===15,d.style.position=d.style.top="",b.style.overflow="hidden",b.style.position="relative",h.subtractsBorderForOverflowNotVisible=d.offsetTop===-5,h.doesNotIncludeMarginInBodyOffset=m.offsetTop!==i,m.removeChild(a),o=a=null,f.extend(k,h))}),o.innerHTML="",n.removeChild(o),o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[f.expando]:a[f.expando]&&f.expando,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[f.expando]=n=++f.uuid:n=f.expando),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[f.expando]:f.expando;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)?b=b:b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" "));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];if(!arguments.length){if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}return b}e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!a||j===3||j===8||j===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g},removeAttr:function(a,b){var c,d,e,g,h=0;if(a.nodeType===1){d=(b||"").split(p),g=d.length;for(;h<g;h++)e=d[h].toLowerCase(),c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1)}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return b;h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/\.(.*)$/,A=/^(?:textarea|input|select)$/i,B=/\./g,C=/ /g,D=/[^\w\s.|`]/g,E=/^([^\.]*)?(?:\.(.+))?$/,F=/\bhover(\.\S+)?/,G=/^key/,H=/^(?:mouse|contextmenu)|click/,I=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,J=function(a){var b=I.exec(a);b&&
(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},K=function(a,b){return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||a.id===b[2])&&(!b[3]||b[3].test(a.className))},L=function(a){return f.event.special.hover?a:a.replace(F,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=L(c).split(" ");for(k=0;k<c.length;k++){l=E.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,namespace:n.join(".")},p),g&&(o.quick=J(g),!o.quick&&f.expr.match.POS.test(g)&&(o.isPositional=!0)),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d){var e=f.hasData(a)&&f._data(a),g,h,i,j,k,l,m,n,o,p,q;if(!!e&&!!(m=e.events)){b=L(b||"").split(" ");for(g=0;g<b.length;g++){h=E.exec(b[g])||[],i=h[1],j=h[2];if(!i){j=j?"."+j:"";for(l in m)f.event.remove(a,l+j,c,d);return}n=f.event.special[i]||{},i=(d?n.delegateType:n.bindType)||i,p=m[i]||[],k=p.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;if(c||j||d||n.remove)for(l=0;l<p.length;l++){q=p[l];if(!c||c.guid===q.guid)if(!j||j.test(q.namespace))if(!d||d===q.selector||d==="**"&&q.selector)p.splice(l--,1),q.selector&&p.delegateCount--,n.remove&&n.remove.call(a,q)}else p.length=0;p.length===0&&k!==p.length&&((!n.teardown||n.teardown.call(a,j)===!1)&&f.removeEvent(a,i,e.handle),delete m[i])}f.isEmptyObject(m)&&(o=e.handle,o&&(o.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"",(g||!e)&&c.preventDefault();if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,n=null;for(m=e.parentNode;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length;l++){m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d);if(c.isPropagationStopped())break}c.type=h,c.isDefaultPrevented()||(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=(f.event.special[c.type]||{}).handle,j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click"))for(m=c.target;m!=this;m=m.parentNode||this){o={},q=[];for(k=0;k<e;k++)r=d[k],s=r.selector,t=o[s],r.isPositional?t=(t||(o[s]=f(s))).index(m)>=0:t===b&&(t=o[s]=r.quick?K(m,r.quick):f(m).is(s)),t&&q.push(r);q.length&&j.push({elem:m,matches:q})}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){p=j[k],c.currentTarget=p.elem;for(l=0;l<p.matches.length&&!c.isImmediatePropagationStopped();l++){r=p.matches[l];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=(i||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement wheelDelta".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},focus:{delegateType:"focusin",noBubble:!0},blur:{delegateType:"focusout",noBubble:!0},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?N:M):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=N;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=N;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=N,this.stopPropagation()},isDefaultPrevented:M,isPropagationStopped:M,isImmediatePropagationStopped:M},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]=f.event.special[b]={delegateType:b,bindType:b,handle:function(a){var b=this,c=a.relatedTarget,d=a.handleObj,e=d.selector,g,h;if(!c||d.origType===a.type||c!==b&&!f.contains(b,c))g=a.type,a.type=d.origType,h=d.handler.apply(this,arguments),a.type=g;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(A.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;A.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return A.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=M;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=M);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.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(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),G.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),H.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw"Syntax error, unrecognized expression: "+a};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?T.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",Z=/ jQuery\d+="(?:\d+|null)"/g,$=/^\s+/,_=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,ba=/<([\w:]+)/,bb=/<tbody/i,bc=/<|&#?\w+;/,bd=/<(?:script|style)/i,be=/<(?:script|object|embed|option|style)/i,bf=new RegExp("<(?:"+Y.replace(" ","|")+")","i"),bg=/checked\s*(?:[^=]|=\s*.checked.)/i,bh=/\/(java|ecma)script/i,bi=/^\s*<!(?:\[CDATA\[|\-\-)/,bj={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bk=X(c);bj.optgroup=bj.option,bj.tbody=bj.tfoot=bj.colgroup=bj.caption=bj.thead,bj.th=bj.td,f.support.htmlSerialize||(bj._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after"
,arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Z,""):null;if(typeof a=="string"&&!bd.test(a)&&(f.support.leadingWhitespace||!$.test(a))&&!bj[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(_,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bg.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bl(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,br)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!be.test(j)&&(f.support.checkClone||!bg.test(j))&&!f.support.unknownElems&&bf.test(j)&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bn(a,d),e=bo(a),g=bo(d);for(h=0;e[h];++h)g[h]&&bn(e[h],g[h])}if(b){bm(a,d);if(c){e=bo(a),g=bo(d);for(h=0;e[h];++h)bm(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bc.test(k))k=b.createTextNode(k);else{k=k.replace(_,"<$1></$2>");var l=(ba.exec(k)||["",""])[1].toLowerCase(),m=bj[l]||bj._default,n=m[0],o=b.createElement("div");b===c?bk.appendChild(o):X(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=bb.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&$.test(k)&&o.insertBefore(b.createTextNode($.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bq(k[i]);else bq(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bh.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bs=/alpha\([^)]*\)/i,bt=/opacity=([^)]*)/,bu=/([A-Z]|^ms)/g,bv=/^-?\d+(?:px)?$/i,bw=/^-?\d/,bx=/^([\-+])=([\-+.\de]+)/,by={position:"absolute",visibility:"hidden",display:"block"},bz=["Left","Right"],bA=["Top","Bottom"],bB,bC,bD;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bB(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bx.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bB)return bB(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bE(a,b,d);f.swap(a,by,function(){e=bE(a,b,d)});return e}},set:function(a,b){if(!bv.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bt.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bs,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bs.test(g)?g.replace(bs,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bB(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bC=function(a,c){var d,e,g;c=c.replace(bu,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bD=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bv.test(f)&&bw.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bB=bC||bD,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bF=/%20/g,bG=/\[\]$/,bH=/\r?\n/g,bI=/#.*$/,bJ=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bK=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bL=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bM=/^(?:GET|HEAD)$/,bN=/^\/\//,bO=/\?/,bP=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bQ=/^(?:select|textarea)/i,bR=/\s+/,bS=/([?&])_=[^&]*/,bT=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bU=f.fn.load,bV={},bW={},bX,bY,bZ=["*/"]+["*"];try{bX=e.href}catch(b$){bX=c.createElement("a"),bX.href="",bX=bX.href}bY=bT.exec(bX.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bU)return bU.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bP,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bQ.test(this.nodeName)||bK.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bH,"\r\n")}}):{name:b.name,value:c.replace(bH,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?cb(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),cb(a,b);return a},ajaxSettings:{url:bX,isLocal:bL.test(bY[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bZ},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:b_(bV),ajaxTransport:b_(bW),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cd(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=ce(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bJ.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bI,"").replace(bN,bY[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bR),d.crossDomain==null&&(r=bT.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bY[1]&&r[2]==bY[2]&&(r[3]||(r[1]==="http:"?80:443))==(bY[3]||(bY[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),ca(bV,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bM.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bO.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bS,"$1_="+x);d.url=y+(y===d.url?(bO.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bZ+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=ca(bW,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)cc(g,a[g],c,e);return d.join("&").replace(bF,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cf=f.now(),cg=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cf++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cg.test(b.url)||e&&cg.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cg,l),b.url===j&&(e&&(k=k.replace(cg,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ch=a.ActiveXObject?function(){for(var a in cj)cj[a](0,1)}:!1,ci=0,cj;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ck()||cl()}:ck,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ch&&delete cj[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++ci,ch&&(cj||(cj={},f(a).unload(ch)),cj[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cm={},cn,co,cp=/^(?:toggle|show|hide)$/,cq=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cr,cs=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],ct;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cw("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cx(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cw("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cw("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cx(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cp.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=cq.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cw("show",1),slideUp:cw("hide",1),slideToggle:cw("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=ct||cu(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cr&&(cr=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=ct||cu(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cr),cr=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now))}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cy=/^t(?:able|d|h)$/i,cz=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cA(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cy.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cz.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cz.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cA(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cA(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
var Base64=(function(){var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var b={encode:function(e){var c="";var m,k,h;var l,j,g,f;var d=0;do{m=e.charCodeAt(d++);k=e.charCodeAt(d++);h=e.charCodeAt(d++);l=m>>2;j=((m&3)<<4)|(k>>4);g=((k&15)<<2)|(h>>6);f=h&63;if(isNaN(k)){g=f=64}else{if(isNaN(h)){f=64}}c=c+a.charAt(l)+a.charAt(j)+a.charAt(g)+a.charAt(f)}while(d<e.length);return c},decode:function(e){var c="";var m,k,h;var l,j,g,f;var d=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{l=a.indexOf(e.charAt(d++));j=a.indexOf(e.charAt(d++));g=a.indexOf(e.charAt(d++));f=a.indexOf(e.charAt(d++));m=(l<<2)|(j>>4);k=((j&15)<<4)|(g>>2);h=((g&3)<<6)|f;c=c+String.fromCharCode(m);if(g!=64){c=c+String.fromCharCode(k)}if(f!=64){c=c+String.fromCharCode(h)}}while(d<e.length);return c}};return b})();var MD5=(function(){var o=0;var a="";var l=8;var j=function(r,u){var t=(r&65535)+(u&65535);var s=(r>>16)+(u>>16)+(t>>16);return(s<<16)|(t&65535)};var n=function(r,s){return(r<<s)|(r>>>(32-s))};var b=function(u){var t=[];var r=(1<<l)-1;for(var s=0;s<u.length*l;s+=l){t[s>>5]|=(u.charCodeAt(s/l)&r)<<(s%32)}return t};var g=function(t){var u="";var r=(1<<l)-1;for(var s=0;s<t.length*32;s+=l){u+=String.fromCharCode((t[s>>5]>>>(s%32))&r)}return u};var q=function(t){var s=o?"0123456789ABCDEF":"0123456789abcdef";var u="";for(var r=0;r<t.length*4;r++){u+=s.charAt((t[r>>2]>>((r%4)*8+4))&15)+s.charAt((t[r>>2]>>((r%4)*8))&15)}return u};var p=function(u){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var w="";var v,r;for(var s=0;s<u.length*4;s+=3){v=(((u[s>>2]>>8*(s%4))&255)<<16)|(((u[s+1>>2]>>8*((s+1)%4))&255)<<8)|((u[s+2>>2]>>8*((s+2)%4))&255);for(r=0;r<4;r++){if(s*8+r*6>u.length*32){w+=a}else{w+=t.charAt((v>>6*(3-r))&63)}}}return w};var d=function(z,v,u,r,y,w){return j(n(j(j(v,z),j(r,w)),y),u)};var k=function(v,u,A,z,r,y,w){return d((u&A)|((~u)&z),v,u,r,y,w)};var c=function(v,u,A,z,r,y,w){return d((u&z)|(A&(~z)),v,u,r,y,w)};var m=function(v,u,A,z,r,y,w){return d(u^A^z,v,u,r,y,w)};var i=function(v,u,A,z,r,y,w){return d(A^(u|(~z)),v,u,r,y,w)};var f=function(C,w){C[w>>5]|=128<<((w)%32);C[(((w+64)>>>9)<<4)+14]=w;var B=1732584193;var A=-271733879;var z=-1732584194;var y=271733878;var v,u,t,r;for(var s=0;s<C.length;s+=16){v=B;u=A;t=z;r=y;B=k(B,A,z,y,C[s+0],7,-680876936);y=k(y,B,A,z,C[s+1],12,-389564586);z=k(z,y,B,A,C[s+2],17,606105819);A=k(A,z,y,B,C[s+3],22,-1044525330);B=k(B,A,z,y,C[s+4],7,-176418897);y=k(y,B,A,z,C[s+5],12,1200080426);z=k(z,y,B,A,C[s+6],17,-1473231341);A=k(A,z,y,B,C[s+7],22,-45705983);B=k(B,A,z,y,C[s+8],7,1770035416);y=k(y,B,A,z,C[s+9],12,-1958414417);z=k(z,y,B,A,C[s+10],17,-42063);A=k(A,z,y,B,C[s+11],22,-1990404162);B=k(B,A,z,y,C[s+12],7,1804603682);y=k(y,B,A,z,C[s+13],12,-40341101);z=k(z,y,B,A,C[s+14],17,-1502002290);A=k(A,z,y,B,C[s+15],22,1236535329);B=c(B,A,z,y,C[s+1],5,-165796510);y=c(y,B,A,z,C[s+6],9,-1069501632);z=c(z,y,B,A,C[s+11],14,643717713);A=c(A,z,y,B,C[s+0],20,-373897302);B=c(B,A,z,y,C[s+5],5,-701558691);y=c(y,B,A,z,C[s+10],9,38016083);z=c(z,y,B,A,C[s+15],14,-660478335);A=c(A,z,y,B,C[s+4],20,-405537848);B=c(B,A,z,y,C[s+9],5,568446438);y=c(y,B,A,z,C[s+14],9,-1019803690);z=c(z,y,B,A,C[s+3],14,-187363961);A=c(A,z,y,B,C[s+8],20,1163531501);B=c(B,A,z,y,C[s+13],5,-1444681467);y=c(y,B,A,z,C[s+2],9,-51403784);z=c(z,y,B,A,C[s+7],14,1735328473);A=c(A,z,y,B,C[s+12],20,-1926607734);B=m(B,A,z,y,C[s+5],4,-378558);y=m(y,B,A,z,C[s+8],11,-2022574463);z=m(z,y,B,A,C[s+11],16,1839030562);A=m(A,z,y,B,C[s+14],23,-35309556);B=m(B,A,z,y,C[s+1],4,-1530992060);y=m(y,B,A,z,C[s+4],11,1272893353);z=m(z,y,B,A,C[s+7],16,-155497632);A=m(A,z,y,B,C[s+10],23,-1094730640);B=m(B,A,z,y,C[s+13],4,681279174);y=m(y,B,A,z,C[s+0],11,-358537222);z=m(z,y,B,A,C[s+3],16,-722521979);A=m(A,z,y,B,C[s+6],23,76029189);B=m(B,A,z,y,C[s+9],4,-640364487);y=m(y,B,A,z,C[s+12],11,-421815835);z=m(z,y,B,A,C[s+15],16,530742520);A=m(A,z,y,B,C[s+2],23,-995338651);B=i(B,A,z,y,C[s+0],6,-198630844);y=i(y,B,A,z,C[s+7],10,1126891415);z=i(z,y,B,A,C[s+14],15,-1416354905);A=i(A,z,y,B,C[s+5],21,-57434055);B=i(B,A,z,y,C[s+12],6,1700485571);y=i(y,B,A,z,C[s+3],10,-1894986606);z=i(z,y,B,A,C[s+10],15,-1051523);A=i(A,z,y,B,C[s+1],21,-2054922799);B=i(B,A,z,y,C[s+8],6,1873313359);y=i(y,B,A,z,C[s+15],10,-30611744);z=i(z,y,B,A,C[s+6],15,-1560198380);A=i(A,z,y,B,C[s+13],21,1309151649);B=i(B,A,z,y,C[s+4],6,-145523070);y=i(y,B,A,z,C[s+11],10,-1120210379);z=i(z,y,B,A,C[s+2],15,718787259);A=i(A,z,y,B,C[s+9],21,-343485551);B=j(B,v);A=j(A,u);z=j(z,t);y=j(y,r)}return[B,A,z,y]};var e=function(t,w){var v=b(t);if(v.length>16){v=f(v,t.length*l)}var r=new Array(16),u=new Array(16);for(var s=0;s<16;s++){r[s]=v[s]^909522486;u[s]=v[s]^1549556828}var x=f(r.concat(b(w)),512+w.length*l);return f(u.concat(x),512+128)};var h={hexdigest:function(r){return q(f(b(r),r.length*l))},b64digest:function(r){return p(f(b(r),r.length*l))},hash:function(r){return g(f(b(r),r.length*l))},hmac_hexdigest:function(r,s){return q(e(r,s))},hmac_b64digest:function(r,s){return p(e(r,s))},hmac_hash:function(r,s){return g(e(r,s))},test:function(){return MD5.hexdigest("abc")==="900150983cd24fb0d6963f7d28e17f72"}};return h})();if(!Function.prototype.bind){Function.prototype.bind=function(e){var d=this;var c=Array.prototype.slice;var b=Array.prototype.concat;var a=c.call(arguments,1);return function(){return d.apply(e?e:this,b.call(a,c.call(arguments,0)))}}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(b){var a=this.length;var c=Number(arguments[1])||0;c=(c<0)?Math.ceil(c):Math.floor(c);if(c<0){c+=a}for(;c<a;c++){if(c in this&&this[c]===b){return c}}return -1}}(function(f){var e;function c(h,g){return new e.Builder(h,g)}function a(g){return new e.Builder("message",g)}function d(g){return new e.Builder("iq",g)}function b(g){return new e.Builder("presence",g)}e={VERSION:"8d27954",NS:{HTTPBIND:"http://jabber.org/protocol/httpbind",BOSH:"urn:xmpp:xbosh",CLIENT:"jabber:client",AUTH:"jabber:iq:auth",ROSTER:"jabber:iq:roster",PROFILE:"jabber:iq:profile",DISCO_INFO:"http://jabber.org/protocol/disco#info",DISCO_ITEMS:"http://jabber.org/protocol/disco#items",MUC:"http://jabber.org/protocol/muc",SASL:"urn:ietf:params:xml:ns:xmpp-sasl",STREAM:"http://etherx.jabber.org/streams",BIND:"urn:ietf:params:xml:ns:xmpp-bind",SESSION:"urn:ietf:params:xml:ns:xmpp-session",VERSION:"jabber:iq:version",STANZAS:"urn:ietf:params:xml:ns:xmpp-stanzas"},addNamespace:function(g,h){e.NS[g]=h},Status:{ERROR:0,CONNECTING:1,CONNFAIL:2,AUTHENTICATING:3,AUTHFAIL:4,CONNECTED:5,DISCONNECTED:6,DISCONNECTING:7,ATTACHED:8},LogLevel:{DEBUG:0,INFO:1,WARN:2,ERROR:3,FATAL:4},ElementType:{NORMAL:1,TEXT:3,CDATA:4},TIMEOUT:1.1,SECONDARY_TIMEOUT:0.1,forEachChild:function(k,l,j){var h,g;for(h=0;h<k.childNodes.length;h++){g=k.childNodes[h];if(g.nodeType==e.ElementType.NORMAL&&(!l||this.isTagEqual(g,l))){j(g)}}},isTagEqual:function(h,g){return h.tagName.toLowerCase()==g.toLowerCase()},_xmlGenerator:null,_makeGenerator:function(){var g;if(document.implementation.createDocument===undefined){g=this._getIEXmlDom();g.appendChild(g.createElement("strophe"))}else{g=document.implementation.createDocument("jabber:client","strophe",null)}return g},xmlGenerator:function(){if(!e._xmlGenerator){e._xmlGenerator=e._makeGenerator()}return e._xmlGenerator},_getIEXmlDom:function(){var h=null;var j=["Msxml2.DOMDocument.6.0","Msxml2.DOMDocument.5.0","Msxml2.DOMDocument.4.0","MSXML2.DOMDocument.3.0","MSXML2.DOMDocument","MSXML.DOMDocument","Microsoft.XMLDOM"];for(var i=0;i<j.length;i++){if(h===null){try{h=new ActiveXObject(j[i])}catch(g){h=null}}else{break}}return h},xmlElement:function(j){if(!j){return null}var m=e.xmlGenerator().createElement(j);var g,l,h;for(g=1;g<arguments.length;g++){if(!arguments[g]){continue}if(typeof(arguments[g])=="string"||typeof(arguments[g])=="number"){m.appendChild(e.xmlTextNode(arguments[g]))}else{if(typeof(arguments[g])=="object"&&typeof(arguments[g].sort)=="function"){for(l=0;l<arguments[g].length;l++){if(typeof(arguments[g][l])=="object"&&typeof(arguments[g][l].sort)=="function"){m.setAttribute(arguments[g][l][0],arguments[g][l][1])}}}else{if(typeof(arguments[g])=="object"){for(h in arguments[g]){if(arguments[g].hasOwnProperty(h)){m.setAttribute(h,arguments[g][h])}}}}}}return m},xmlescape:function(g){g=g.replace(/\&/g,"&amp;");g=g.replace(/</g,"&lt;");g=g.replace(/>/g,"&gt;");g=g.replace(/'/g,"&apos;");g=g.replace(/"/g,"&quot;");return g},xmlTextNode:function(g){g=e.xmlescape(g);return e.xmlGenerator().createTextNode(g)},getText:function(h){if(!h){return null}var j="";if(h.childNodes.length===0&&h.nodeType==e.ElementType.TEXT){j+=h.nodeValue}for(var g=0;g<h.childNodes.length;g++){if(h.childNodes[g].nodeType==e.ElementType.TEXT){j+=h.childNodes[g].nodeValue}}return j},copyElement:function(j){var g,h;if(j.nodeType==e.ElementType.NORMAL){h=e.xmlElement(j.tagName);for(g=0;g<j.attributes.length;g++){h.setAttribute(j.attributes[g].nodeName.toLowerCase(),j.attributes[g].value)}for(g=0;g<j.childNodes.length;g++){h.appendChild(e.copyElement(j.childNodes[g]))}}else{if(j.nodeType==e.ElementType.TEXT){h=e.xmlGenerator().createTextNode(j.nodeValue)}}return h},escapeNode:function(g){return g.replace(/^\s+|\s+$/g,"").replace(/\\/g,"\\5c").replace(/ /g,"\\20").replace(/\"/g,"\\22").replace(/\&/g,"\\26").replace(/\'/g,"\\27").replace(/\//g,"\\2f").replace(/:/g,"\\3a").replace(/</g,"\\3c").replace(/>/g,"\\3e").replace(/@/g,"\\40")},unescapeNode:function(g){return g.replace(/\\20/g," ").replace(/\\22/g,'"').replace(/\\26/g,"&").replace(/\\27/g,"'").replace(/\\2f/g,"/").replace(/\\3a/g,":").replace(/\\3c/g,"<").replace(/\\3e/g,">").replace(/\\40/g,"@").replace(/\\5c/g,"\\")},getNodeFromJid:function(g){if(g.indexOf("@")<0){return null}return g.split("@")[0]},getDomainFromJid:function(g){var h=e.getBareJidFromJid(g);if(h.indexOf("@")<0){return h}else{var i=h.split("@");i.splice(0,1);return i.join("@")}},getResourceFromJid:function(g){var h=g.split("/");if(h.length<2){return null}h.splice(0,1);return h.join("/")},getBareJidFromJid:function(g){return g?g.split("/")[0]:null},log:function(h,g){return},debug:function(g){this.log(this.LogLevel.DEBUG,g)},info:function(g){this.log(this.LogLevel.INFO,g)},warn:function(g){this.log(this.LogLevel.WARN,g)},error:function(g){this.log(this.LogLevel.ERROR,g)},fatal:function(g){this.log(this.LogLevel.FATAL,g)},serialize:function(j){var g;if(!j){return null}if(typeof(j.tree)==="function"){j=j.tree()}var l=j.nodeName;var h,k;if(j.getAttribute("_realname")){l=j.getAttribute("_realname")}g="<"+l;for(h=0;h<j.attributes.length;h++){if(j.attributes[h].nodeName!="_realname"){g+=" "+j.attributes[h].nodeName.toLowerCase()+"='"+j.attributes[h].value.replace(/&/g,"&amp;").replace(/\'/g,"&apos;").replace(/</g,"&lt;")+"'"}}if(j.childNodes.length>0){g+=">";for(h=0;h<j.childNodes.length;h++){k=j.childNodes[h];switch(k.nodeType){case e.ElementType.NORMAL:g+=e.serialize(k);break;case e.ElementType.TEXT:g+=k.nodeValue;break;case e.ElementType.CDATA:g+="<![CDATA["+k.nodeValue+"]]>"}}g+="</"+l+">"}else{g+="/>"}return g},_requestId:0,_connectionPlugins:{},addConnectionPlugin:function(g,h){e._connectionPlugins[g]=h}};e.Builder=function(h,g){if(h=="presence"||h=="message"||h=="iq"){if(g&&!g.xmlns){g.xmlns=e.NS.CLIENT}else{if(!g){g={xmlns:e.NS.CLIENT}}}}this.nodeTree=e.xmlElement(h,g);this.node=this.nodeTree};e.Builder.prototype={tree:function(){return this.nodeTree},toString:function(){return e.serialize(this.nodeTree)},up:function(){this.node=this.node.parentNode;return this},attrs:function(h){for(var g in h){if(h.hasOwnProperty(g)){this.node.setAttribute(g,h[g])}}return this},c:function(h,g,i){var j=e.xmlElement(h,g,i);this.node.appendChild(j);if(!i){this.node=j}return this},cnode:function(i){var k=e.xmlGenerator();try{var h=(k.importNode!==undefined)}catch(j){var h=false}var g=h?k.importNode(i,true):e.copyElement(i);this.node.appendChild(g);this.node=g;return this},t:function(g){var h=e.xmlTextNode(g);this.node.appendChild(h);return this}};e.Handler=function(k,j,h,i,m,l,g){this.handler=k;this.ns=j;this.name=h;this.type=i;this.id=m;this.options=g||{matchbare:false};if(!this.options.matchBare){this.options.matchBare=false}if(this.options.matchBare){this.from=l?e.getBareJidFromJid(l):null}else{this.from=l}this.user=true};e.Handler.prototype={isMatch:function(h){var j;var i=null;if(this.options.matchBare){i=e.getBareJidFromJid(h.getAttribute("from"))}else{i=h.getAttribute("from")}j=false;if(!this.ns){j=true}else{var g=this;e.forEachChild(h,null,function(k){if(k.getAttribute("xmlns")==g.ns){j=true}});j=j||h.getAttribute("xmlns")==this.ns}if(j&&(!this.name||e.isTagEqual(h,this.name))&&(!this.type||h.getAttribute("type")==this.type)&&(!this.id||h.getAttribute("id")==this.id)&&(!this.from||i==this.from)){return true}return false},run:function(h){var g=null;try{g=this.handler(h)}catch(i){if(i.sourceURL){e.fatal("error: "+this.handler+" "+i.sourceURL+":"+i.line+" - "+i.name+": "+i.message)}else{if(i.fileName){if(typeof(console)!="undefined"){console.trace();console.error(this.handler," - error - ",i,i.message)}e.fatal("error: "+this.handler+" "+i.fileName+":"+i.lineNumber+" - "+i.name+": "+i.message)}else{e.fatal("error: "+this.handler)}}throw i}return g},toString:function(){return"{Handler: "+this.handler+"("+this.name+","+this.id+","+this.ns+")}"}};e.TimedHandler=function(h,g){this.period=h;this.handler=g;this.lastCalled=new Date().getTime();this.user=true};e.TimedHandler.prototype={run:function(){this.lastCalled=new Date().getTime();return this.handler()},reset:function(){this.lastCalled=new Date().getTime()},toString:function(){return"{TimedHandler: "+this.handler+"("+this.period+")}"}};e.Request=function(i,h,g,j){this.id=++e._requestId;this.xmlData=i;this.data=e.serialize(i);this.origFunc=h;this.func=h;this.rid=g;this.date=NaN;this.sends=j||0;this.abort=false;this.dead=null;this.age=function(){if(!this.date){return 0}var k=new Date();return(k-this.date)/1000};this.timeDead=function(){if(!this.dead){return 0}var k=new Date();return(k-this.dead)/1000};this.xhr=this._newXHR()};e.Request.prototype={getResponse:function(){var g=null;if(this.xhr.responseXML&&this.xhr.responseXML.documentElement){g=this.xhr.responseXML.documentElement;if(g.tagName=="parsererror"){e.error("invalid response received");e.error("responseText: "+this.xhr.responseText);e.error("responseXML: "+e.serialize(this.xhr.responseXML));throw"parsererror"}}else{if(this.xhr.responseText){e.error("invalid response received");e.error("responseText: "+this.xhr.responseText);e.error("responseXML: "+e.serialize(this.xhr.responseXML))}}return g},_newXHR:function(){var g=null;if(window.XMLHttpRequest){g=new XMLHttpRequest();if(g.overrideMimeType){g.overrideMimeType("text/xml")}}else{if(window.ActiveXObject){g=new ActiveXObject("Microsoft.XMLHTTP")}}g.onreadystatechange=this.func.bind(null,this);return g}};e.Connection=function(g){this.service=g;this.jid="";this.rid=Math.floor(Math.random()*4294967295);this.sid=null;this.streamId=null;this.features=null;this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this._idleTimeout=null;this._disconnectTimeout=null;this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this.paused=false;this.hold=1;this.wait=60;this.window=5;this._data=[];this._requests=[];this._uniqueId=Math.round(Math.random()*10000);this._sasl_success_handler=null;this._sasl_failure_handler=null;this._sasl_challenge_handler=null;this._idleTimeout=setTimeout(this._onIdle.bind(this),100);for(var h in e._connectionPlugins){if(e._connectionPlugins.hasOwnProperty(h)){var j=e._connectionPlugins[h];var i=function(){};i.prototype=j;this[h]=new i();this[h].init(this)}}};e.Connection.prototype={reset:function(){this.rid=Math.floor(Math.random()*4294967295);this.sid=null;this.streamId=null;this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this._requests=[];this._uniqueId=Math.round(Math.random()*10000)},pause:function(){this.paused=true},resume:function(){this.paused=false},getUniqueId:function(g){if(typeof(g)=="string"||typeof(g)=="number"){return ++this._uniqueId+":"+g}else{return ++this._uniqueId+""}},connect:function(h,i,l,k,j){this.jid=h;this.pass=i;this.connect_callback=l;this.disconnecting=false;this.connected=false;this.authenticated=false;this.errors=0;this.wait=k||this.wait;this.hold=j||this.hold;this.domain=e.getDomainFromJid(this.jid);var g=this._buildBody().attrs({to:this.domain,"xml:lang":"en",wait:this.wait,hold:this.hold,content:"text/xml; charset=utf-8",ver:"1.6","xmpp:version":"1.0","xmlns:xmpp":e.NS.BOSH});this._changeConnectStatus(e.Status.CONNECTING,null);this._requests.push(new e.Request(g.tree(),this._onRequestStateChange.bind(this,this._connect_cb.bind(this)),g.tree().getAttribute("rid")));this._throttledRequestHandler()},attach:function(i,g,j,m,l,k,h){this.jid=i;this.sid=g;this.rid=j;this.connect_callback=m;this.domain=e.getDomainFromJid(this.jid);this.authenticated=true;this.connected=true;this.wait=l||this.wait;this.hold=k||this.hold;this.window=h||this.window;this._changeConnectStatus(e.Status.ATTACHED,null)},xmlInput:function(g){return},xmlOutput:function(g){return},rawInput:function(g){return},rawOutput:function(g){return},send:function(h){if(h===null){return}if(typeof(h.sort)==="function"){for(var g=0;g<h.length;g++){this._queueData(h[g])}}else{if(typeof(h.tree)==="function"){this._queueData(h.tree())}else{this._queueData(h)}}this._throttledRequestHandler();clearTimeout(this._idleTimeout);this._idleTimeout=setTimeout(this._onIdle.bind(this),100)},flush:function(){clearTimeout(this._idleTimeout);this._onIdle()},sendIQ:function(j,n,g,k){var l=null;var i=this;if(typeof(j.tree)==="function"){j=j.tree()}var m=j.getAttribute("id");if(!m){m=this.getUniqueId("sendIQ");j.setAttribute("id",m)}var h=this.addHandler(function(p){if(l){i.deleteTimedHandler(l)}var o=p.getAttribute("type");if(o=="result"){if(n){n(p)}}else{if(o=="error"){if(g){g(p)}}else{throw {name:"StropheError",message:"Got bad IQ type of "+o}}}},null,"iq",null,m);if(k){l=this.addTimedHandler(k,function(){i.deleteHandler(h);if(g){g(null)}return false})}this.send(j);return m},_queueData:function(g){if(g===null||!g.tagName||!g.childNodes){throw {name:"StropheError",message:"Cannot queue non-DOMElement."}}this._data.push(g)},_sendRestart:function(){this._data.push("restart");this._throttledRequestHandler();clearTimeout(this._idleTimeout);this._idleTimeout=setTimeout(this._onIdle.bind(this),100)},addTimedHandler:function(i,h){var g=new e.TimedHandler(i,h);this.addTimeds.push(g);return g},deleteTimedHandler:function(g){this.removeTimeds.push(g)},addHandler:function(l,k,i,j,n,m,h){var g=new e.Handler(l,k,i,j,n,m,h);this.addHandlers.push(g);return g},deleteHandler:function(g){this.removeHandlers.push(g)},disconnect:function(g){this._changeConnectStatus(e.Status.DISCONNECTING,g);e.info("Disconnect was called because: "+g);if(this.connected){this._disconnectTimeout=this._addSysTimedHandler(3000,this._onDisconnectTimeout.bind(this));this._sendTerminate()}},_changeConnectStatus:function(g,m){for(var h in e._connectionPlugins){if(e._connectionPlugins.hasOwnProperty(h)){var j=this[h];if(j.statusChanged){try{j.statusChanged(g,m)}catch(i){e.error(""+h+" plugin caused an exception changing status: "+i)}}}}if(this.connect_callback){try{this.connect_callback(g,m)}catch(l){e.error("User connection callback caused an exception: "+l)}}},_buildBody:function(){var g=c("body",{rid:this.rid++,xmlns:e.NS.HTTPBIND});if(this.sid!==null){g.attrs({sid:this.sid})}return g},_removeRequest:function(h){e.debug("removing request");var g;for(g=this._requests.length-1;g>=0;g--){if(h==this._requests[g]){this._requests.splice(g,1)}}h.xhr.onreadystatechange=function(){};this._throttledRequestHandler()},_restartRequest:function(g){var h=this._requests[g];if(h.dead===null){h.dead=new Date()}this._processRequest(g)},_processRequest:function(k){var p=this._requests[k];var s=-1;try{if(p.xhr.readyState==4){s=p.xhr.status}}catch(n){e.error("caught an error in _requests["+k+"], reqStatus: "+s)}if(typeof(s)=="undefined"){s=-1}if(p.sends>5){this._onDisconnectTimeout();return}var j=p.age();var h=(!isNaN(j)&&j>Math.floor(e.TIMEOUT*this.wait));var l=(p.dead!==null&&p.timeDead()>Math.floor(e.SECONDARY_TIMEOUT*this.wait));var r=(p.xhr.readyState==4&&(s<1||s>=500));if(h||l||r){if(l){e.error("Request "+this._requests[k].id+" timed out (secondary), restarting")}p.abort=true;p.xhr.abort();p.xhr.onreadystatechange=function(){};this._requests[k]=new e.Request(p.xmlData,p.origFunc,p.rid,p.sends);p=this._requests[k]}if(p.xhr.readyState===0){e.debug("request id "+p.id+"."+p.sends+" posting");try{var g=!("sync" in this&&this.sync===true);p.xhr.open("POST",this.service,g)}catch(o){e.error("XHR open failed.");if(!this.connected){this._changeConnectStatus(e.Status.CONNFAIL,"bad-service")}this.disconnect();return}var q=function(){p.date=new Date();p.xhr.send(p.data)};if(p.sends>1){var m=Math.min(Math.floor(e.TIMEOUT*this.wait),Math.pow(p.sends,3))*1000;setTimeout(q,m)}else{q()}p.sends++;if(this.xmlOutput!==e.Connection.prototype.xmlOutput){this.xmlOutput(p.xmlData)}if(this.rawOutput!==e.Connection.prototype.rawOutput){this.rawOutput(p.data)}}else{e.debug("_processRequest: "+(k===0?"first":"second")+" request has readyState of "+p.xhr.readyState)}},_throttledRequestHandler:function(){if(!this._requests){e.debug("_throttledRequestHandler called with undefined requests")}else{e.debug("_throttledRequestHandler called with "+this._requests.length+" requests")}if(!this._requests||this._requests.length===0){return}if(this._requests.length>0){this._processRequest(0)}if(this._requests.length>1&&Math.abs(this._requests[0].rid-this._requests[1].rid)<this.window){this._processRequest(1)}},_onRequestStateChange:function(j,i){e.debug("request id "+i.id+"."+i.sends+" state changed to "+i.xhr.readyState);if(i.abort){i.abort=false;return}var h;if(i.xhr.readyState==4){h=0;try{h=i.xhr.status}catch(k){}if(typeof(h)=="undefined"){h=0}if(this.disconnecting){if(h>=400){this._hitError(h);return}}var g=(this._requests[0]==i);var l=(this._requests[1]==i);if((h>0&&h<500)||i.sends>5){this._removeRequest(i);e.debug("request id "+i.id+" should now be removed")}if(h==200){if(l||(g&&this._requests.length>0&&this._requests[0].age()>Math.floor(e.SECONDARY_TIMEOUT*this.wait))){this._restartRequest(0)}e.debug("request id "+i.id+"."+i.sends+" got 200");j(i);this.errors=0}else{e.error("request id "+i.id+"."+i.sends+" error "+h+" happened");if(h===0||(h>=400&&h<600)||h>=12000){this._hitError(h);if(h>=400&&h<500){this._changeConnectStatus(e.Status.DISCONNECTING,null);this._doDisconnect()}}}if(!((h>0&&h<500)||i.sends>5)){this._throttledRequestHandler()}}},_hitError:function(g){this.errors++;e.warn("request errored, status: "+g+", number of errors: "+this.errors);if(this.errors>4){this._onDisconnectTimeout()}},_doDisconnect:function(){e.info("_doDisconnect was called");this.authenticated=false;this.disconnecting=false;this.sid=null;this.streamId=null;this.rid=Math.floor(Math.random()*4294967295);if(this.connected){this._changeConnectStatus(e.Status.DISCONNECTED,null);this.connected=false}this.handlers=[];this.timedHandlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[]},_dataRecv:function(p){try{var g=p.getResponse()}catch(n){if(n!="parsererror"){throw n}this.disconnect("strophe-parsererror")}if(g===null){return}if(this.xmlInput!==e.Connection.prototype.xmlInput){this.xmlInput(g)}if(this.rawInput!==e.Connection.prototype.rawInput){this.rawInput(e.serialize(g))}var l,j;while(this.removeHandlers.length>0){j=this.removeHandlers.pop();l=this.handlers.indexOf(j);if(l>=0){this.handlers.splice(l,1)}}while(this.addHandlers.length>0){this.handlers.push(this.addHandlers.pop())}if(this.disconnecting&&this._requests.length===0){this.deleteTimedHandler(this._disconnectTimeout);this._disconnectTimeout=null;this._doDisconnect();return}var h=g.getAttribute("type");var o,k;if(h!==null&&h=="terminate"){if(this.disconnecting){return}o=g.getAttribute("condition");k=g.getElementsByTagName("conflict");if(o!==null){if(o=="remote-stream-error"&&k.length>0){o="conflict"}this._changeConnectStatus(e.Status.CONNFAIL,o)}else{this._changeConnectStatus(e.Status.CONNFAIL,"unknown")}this.disconnect();return}var m=this;e.forEachChild(g,null,function(u){var r,s;s=m.handlers;m.handlers=[];for(r=0;r<s.length;r++){var q=s[r];try{if(q.isMatch(u)&&(m.authenticated||!q.user)){if(q.run(u)){m.handlers.push(q)}}else{m.handlers.push(q)}}catch(t){}}})},_sendTerminate:function(){e.info("_sendTerminate was called");var g=this._buildBody().attrs({type:"terminate"});if(this.authenticated){g.c("presence",{xmlns:e.NS.CLIENT,type:"unavailable"})}this.disconnecting=true;var h=new e.Request(g.tree(),this._onRequestStateChange.bind(this,this._dataRecv.bind(this)),g.tree().getAttribute("rid"));this._requests.push(h);this._throttledRequestHandler()},_connect_cb:function(v){e.info("_connect_cb was called");this.connected=true;var h=v.getResponse();if(!h){return}if(this.xmlInput!==e.Connection.prototype.xmlInput){this.xmlInput(h)}if(this.rawInput!==e.Connection.prototype.rawInput){this.rawInput(e.serialize(h))}var m=h.getAttribute("type");var u,o;if(m!==null&&m=="terminate"){u=h.getAttribute("condition");o=h.getElementsByTagName("conflict");if(u!==null){if(u=="remote-stream-error"&&o.length>0){u="conflict"}this._changeConnectStatus(e.Status.CONNFAIL,u)}else{this._changeConnectStatus(e.Status.CONNFAIL,"unknown")}return}if(!this.sid){this.sid=h.getAttribute("sid")}if(!this.stream_id){this.stream_id=h.getAttribute("authid")}var j=h.getAttribute("requests");if(j){this.window=parseInt(j,10)}var g=h.getAttribute("hold");if(g){this.hold=parseInt(g,10)}var q=h.getAttribute("wait");if(q){this.wait=parseInt(q,10)}var w=false;var l=false;var t=false;var x=h.getElementsByTagName("mechanism");var n,s,p,k;if(x.length>0){for(n=0;n<x.length;n++){s=e.getText(x[n]);if(s=="DIGEST-MD5"){l=true}else{if(s=="PLAIN"){w=true}else{if(s=="ANONYMOUS"){t=true}}}}}else{var r=this._buildBody();this._requests.push(new e.Request(r.tree(),this._onRequestStateChange.bind(this,this._connect_cb.bind(this)),r.tree().getAttribute("rid")));this._throttledRequestHandler();return}if(e.getNodeFromJid(this.jid)===null&&t){this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("auth",{xmlns:e.NS.SASL,mechanism:"ANONYMOUS"}).tree())}else{if(e.getNodeFromJid(this.jid)===null){this._changeConnectStatus(e.Status.CONNFAIL,"x-strophe-bad-non-anon-jid");this.disconnect()}else{if(l){this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_challenge_handler=this._addSysHandler(this._sasl_challenge1_cb.bind(this),null,"challenge",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("auth",{xmlns:e.NS.SASL,mechanism:"DIGEST-MD5"}).tree())}else{if(w){p=unescape(encodeURIComponent(e.getBareJidFromJid(this.jid)));p=p+"\u0000";p=p+unescape(encodeURIComponent(e.getNodeFromJid(this.jid)));p=p+"\u0000";p=p+this.pass;this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);k=Base64.encode(p);this.send(c("auth",{xmlns:e.NS.SASL,mechanism:"PLAIN"}).t(k).tree())}else{this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._addSysHandler(this._auth1_cb.bind(this),null,null,null,"_auth_1");this.send(d({type:"get",to:this.domain,id:"_auth_1"}).c("query",{xmlns:e.NS.AUTH}).c("username",{}).t(e.getNodeFromJid(this.jid)).tree())}}}}},_sasl_challenge1_cb:function(k){var h=/([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;var q=Base64.decode(e.getText(k));var r=MD5.hexdigest(""+(Math.random()*1234567890));var n="";var s=null;var o="";var g="";var m;this.deleteHandler(this._sasl_failure_handler);while(q.match(h)){m=q.match(h);q=q.replace(m[0],"");m[2]=m[2].replace(/^"(.+)"$/,"$1");switch(m[1]){case"realm":n=m[2];break;case"nonce":o=m[2];break;case"qop":g=m[2];break;case"host":s=m[2];break}}var l="xmpp/"+this.domain;if(s!==null){l=l+"/"+s}var j=MD5.hash(unescape(encodeURIComponent(e.getNodeFromJid(this.jid)))+":"+n+":"+this.pass)+":"+o+":"+r;var i="AUTHENTICATE:"+l;var p="";p+="username="+this._quote(unescape(encodeURIComponent(e.getNodeFromJid(this.jid))))+",";p+="realm="+this._quote(n)+",";p+="nonce="+this._quote(o)+",";p+="cnonce="+this._quote(r)+",";p+='nc="00000001",';p+='qop="auth",';p+="digest-uri="+this._quote(l)+",";p+="response="+this._quote(MD5.hexdigest(MD5.hexdigest(j)+":"+o+":00000001:"+r+":auth:"+MD5.hexdigest(i)))+",";p+='charset="utf-8"';this._sasl_challenge_handler=this._addSysHandler(this._sasl_challenge2_cb.bind(this),null,"challenge",null,null);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("response",{xmlns:e.NS.SASL}).t(Base64.encode(p)).tree());return false},_quote:function(g){return'"'+g.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'},_sasl_challenge2_cb:function(g){this.deleteHandler(this._sasl_success_handler);this.deleteHandler(this._sasl_failure_handler);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("response",{xmlns:e.NS.SASL}).tree());return false},_auth1_cb:function(g){var h=d({type:"set",id:"_auth_2"}).c("query",{xmlns:e.NS.AUTH}).c("username",{}).t(e.getNodeFromJid(this.jid)).up().c("password").t(this.pass);if(!e.getResourceFromJid(this.jid)){this.jid=e.getBareJidFromJid(this.jid)+"/strophe"}h.up().c("resource",{}).t(e.getResourceFromJid(this.jid));this._addSysHandler(this._auth2_cb.bind(this),null,null,null,"_auth_2");this.send(h.tree());return false},_sasl_success_cb:function(g){e.info("SASL authentication succeeded.");this.deleteHandler(this._sasl_failure_handler);this._sasl_failure_handler=null;if(this._sasl_challenge_handler){this.deleteHandler(this._sasl_challenge_handler);this._sasl_challenge_handler=null}this._addSysHandler(this._sasl_auth1_cb.bind(this),null,"stream:features",null,null);this._sendRestart();return false},_sasl_auth1_cb:function(h){this.features=h;var g,k;for(g=0;g<h.childNodes.length;g++){k=h.childNodes[g];if(k.nodeName=="bind"){this.do_bind=true}if(k.nodeName=="session"){this.do_session=true}}if(!this.do_bind){this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}else{this._addSysHandler(this._sasl_bind_cb.bind(this),null,null,null,"_bind_auth_2");var j=e.getResourceFromJid(this.jid);if(j){this.send(d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:e.NS.BIND}).c("resource",{}).t(j).tree())}else{this.send(d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:e.NS.BIND}).tree())}}return false},_sasl_bind_cb:function(g){if(g.getAttribute("type")=="error"){e.info("SASL binding failed.");this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}var i=g.getElementsByTagName("bind");var h;if(i.length>0){h=i[0].getElementsByTagName("jid");if(h.length>0){this.jid=e.getText(h[0]);if(this.do_session){this._addSysHandler(this._sasl_session_cb.bind(this),null,null,null,"_session_auth_2");this.send(d({type:"set",id:"_session_auth_2"}).c("session",{xmlns:e.NS.SESSION}).tree())}else{this.authenticated=true;this._changeConnectStatus(e.Status.CONNECTED,null)}}}else{e.info("SASL binding failed.");this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}},_sasl_session_cb:function(g){if(g.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(e.Status.CONNECTED,null)}else{if(g.getAttribute("type")=="error"){e.info("Session creation failed.");this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}}return false},_sasl_failure_cb:function(g){if(this._sasl_success_handler){this.deleteHandler(this._sasl_success_handler);this._sasl_success_handler=null}if(this._sasl_challenge_handler){this.deleteHandler(this._sasl_challenge_handler);this._sasl_challenge_handler=null}this._changeConnectStatus(e.Status.AUTHFAIL,null);return false},_auth2_cb:function(g){if(g.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(e.Status.CONNECTED,null)}else{if(g.getAttribute("type")=="error"){this._changeConnectStatus(e.Status.AUTHFAIL,null);this.disconnect()}}return false},_addSysTimedHandler:function(i,h){var g=new e.TimedHandler(i,h);g.user=false;this.addTimeds.push(g);return g},_addSysHandler:function(k,j,h,i,l){var g=new e.Handler(k,j,h,i,l);g.user=false;this.addHandlers.push(g);return g},_onDisconnectTimeout:function(){e.info("_onDisconnectTimeout was called");var g;while(this._requests.length>0){g=this._requests.pop();g.abort=true;g.xhr.abort();g.xhr.onreadystatechange=function(){}}this._doDisconnect();return false},_onIdle:function(){var j,l,n,k;while(this.addTimeds.length>0){this.timedHandlers.push(this.addTimeds.pop())}while(this.removeTimeds.length>0){l=this.removeTimeds.pop();j=this.timedHandlers.indexOf(l);if(j>=0){this.timedHandlers.splice(j,1)}}var h=new Date().getTime();k=[];for(j=0;j<this.timedHandlers.length;j++){l=this.timedHandlers[j];if(this.authenticated||!l.user){n=l.lastCalled+l.period;if(n-h<=0){if(l.run()){k.push(l)}}else{k.push(l)}}}this.timedHandlers=k;var g,m;if(this.authenticated&&this._requests.length===0&&this._data.length===0&&!this.disconnecting){e.info("no requests during idle cycle, sending blank request");this._data.push(null)}if(this._requests.length<2&&this._data.length>0&&!this.paused){g=this._buildBody();for(j=0;j<this._data.length;j++){if(this._data[j]!==null){if(this._data[j]==="restart"){g.attrs({to:this.domain,"xml:lang":"en","xmpp:restart":"true","xmlns:xmpp":e.NS.BOSH})}else{g.cnode(this._data[j]).up()}}}delete this._data;this._data=[];this._requests.push(new e.Request(g.tree(),this._onRequestStateChange.bind(this,this._dataRecv.bind(this)),g.tree().getAttribute("rid")));this._processRequest(this._requests.length-1)}if(this._requests.length>0){m=this._requests[0].age();if(this._requests[0].dead!==null){if(this._requests[0].timeDead()>Math.floor(e.SECONDARY_TIMEOUT*this.wait)){this._throttledRequestHandler()}}if(m>Math.floor(e.TIMEOUT*this.wait)){e.warn("Request "+this._requests[0].id+" timed out, over "+Math.floor(e.TIMEOUT*this.wait)+" seconds since last activity");this._throttledRequestHandler()}}clearTimeout(this._idleTimeout);if(this.connected){this._idleTimeout=setTimeout(this._onIdle.bind(this),100)}}};if(f){f(e,c,a,d,b)}})(function(){window.Strophe=arguments[0];window.$build=arguments[1];window.$msg=arguments[2];window.$iq=arguments[3];window.$pres=arguments[4]});Strophe.addConnectionPlugin("muc",{_connection:null,init:function(a){this._connection=a;Strophe.addNamespace("MUC_OWNER",Strophe.NS.MUC+"#owner");Strophe.addNamespace("MUC_ADMIN",Strophe.NS.MUC+"#admin")},join:function(g,a,c,d,b){var f=this.test_append_nick(g,a);var h=$pres({from:this._connection.jid,to:f}).c("x",{xmlns:Strophe.NS.MUC});if(b){var e=Strophe.xmlElement("password",[],b);h.cnode(e)}if(c){this._connection.addHandler(function(j){var k=j.getAttribute("from");var i=k.split("/");if(i[0]==g){return c(j)}else{return true}},null,"message",null,null,null)}if(d){this._connection.addHandler(function(k){var m=k.getElementsByTagName("x");if(m.length>0){for(var j=0;j<m.length;j++){var l=m[j].getAttribute("xmlns");if(l&&l.match(Strophe.NS.MUC)){return d(k)}}}return true},null,"presence",null,null,null)}this._connection.send(h)},leave:function(f,a,c){var e=this.test_append_nick(f,a);var d=this._connection.getUniqueId();var b=$pres({type:"unavailable",id:d,from:this._connection.jid,to:e}).c("x",{xmlns:Strophe.NS.MUC});this._connection.addHandler(c,null,"presence",null,d,null);this._connection.send(b);return d},message:function(f,a,d,b){var e=this.test_append_nick(f,a);b=b||"groupchat";var c=this._connection.getUniqueId();var g=$msg({to:e,from:this._connection.jid,type:b,id:c}).c("body",{xmlns:Strophe.NS.CLIENT}).t(d);g.up().c("x",{xmlns:"jabber:x:event"}).c("composing");this._connection.send(g);return c},configure:function(b){var a=$iq({to:b,type:"get"}).c("query",{xmlns:Strophe.NS.MUC_OWNER});var c=a.tree();return this._connection.sendIQ(c,function(){},function(){})},cancelConfigure:function(b){var a=$iq({to:b,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"cancel"});var c=a.tree();return this._connection.sendIQ(c,function(){},function(){})},saveConfiguration:function(d,c){var a=$iq({to:d,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"submit"});for(var b=0;b<c.length;b++){a.cnode(c[b]);a.up()}var e=a.tree();return this._connection.sendIQ(e,function(){},function(){})},createInstantRoom:function(b){var a=$iq({to:b,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"submit"});return this._connection.sendIQ(a.tree(),function(){},function(){})},setTopic:function(b,a){var c=$msg({to:b,from:this._connection.jid,type:"groupchat"}).c("subject",{xmlns:"jabber:client"}).t(a);this._connection.send(c.tree())},modifyUser:function(g,b,h,d,f){var a={nick:Strophe.escapeNode(b)};if(h!==null){a.role=h}if(d!==null){a.affiliation=d}var e=$build("item",a);if(f!==null){e.cnode(Strophe.xmlElement("reason",f))}var c=$iq({to:g,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).cnode(e.tree());return this._connection.sendIQ(c.tree(),function(){},function(){})},changeNick:function(d,a){var c=this.test_append_nick(d,a);var b=$pres({from:this._connection.jid,to:c}).c("x",{xmlns:Strophe.NS.MUC});this._connection.send(b.tree())},listRooms:function(c,a){var b=$iq({to:c,from:this._connection.jid,type:"get"}).c("query",{xmlns:Strophe.NS.DISCO_ITEMS});this._connection.sendIQ(b,a,function(){})},test_append_nick:function(c,a){var b=c;if(a){b+="/"+Strophe.escapeNode(a)}return b}});var Mustache=function(){var a=function(){};a.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":true},context:{},render:function(e,d,c,f){if(!f){this.context=d;this.buffer=[]}if(!this.includes("",e)){if(f){return e}else{this.send(e);return}}e=this.render_pragmas(e);var b=this.render_section(e,d,c);if(f){return this.render_tags(b,d,c,f)}this.render_tags(b,d,c,f)},send:function(b){if(b!==""){this.buffer.push(b)}},render_pragmas:function(b){if(!this.includes("%",b)){return b}var d=this;var c=new RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag,"g");return b.replace(c,function(g,e,f){if(!d.pragmas_implemented[e]){throw ({message:"This implementation of mustache doesn't understand the '"+e+"' pragma"})}d.pragmas[e]={};if(f){var h=f.split("=");d.pragmas[e][h[0]]=h[1]}return""})},render_partial:function(b,d,c){b=this.trim(b);if(!c||c[b]===undefined){throw ({message:"unknown_partial '"+b+"'"})}if(typeof(d[b])!="object"){return this.render(c[b],d,c,true)}return this.render(c[b],d[b],c,true)},render_section:function(d,c,b){if(!this.includes("#",d)&&!this.includes("^",d)){return d}var f=this;var e=new RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg");return d.replace(e,function(h,i,g,j){var k=f.find(g,c);if(i=="^"){if(!k||f.is_array(k)&&k.length===0){return f.render(j,c,b,true)}else{return""}}else{if(i=="#"){if(f.is_array(k)){return f.map(k,function(l){return f.render(j,f.create_context(l),b,true)}).join("")}else{if(f.is_object(k)){return f.render(j,f.create_context(k),b,true)}else{if(typeof k==="function"){return k.call(c,j,function(l){return f.render(l,c,b,true)})}else{if(k){return f.render(j,c,b,true)}else{return""}}}}}}})},render_tags:function(k,b,d,f){var e=this;var j=function(){return new RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")};var g=j();var h=function(n,i,m){switch(i){case"!":return"";case"=":e.set_delimiters(m);g=j();return"";case">":return e.render_partial(m,b,d);case"{":return e.find(m,b);default:return e.escape(e.find(m,b))}};var l=k.split("\n");for(var c=0;c<l.length;c++){l[c]=l[c].replace(g,h,this);if(!f){this.send(l[c])}}if(f){return l.join("\n")}},set_delimiters:function(c){var b=c.split(" ");this.otag=this.escape_regex(b[0]);this.ctag=this.escape_regex(b[1])},escape_regex:function(c){if(!arguments.callee.sRE){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\"];arguments.callee.sRE=new RegExp("(\\"+b.join("|\\")+")","g")}return c.replace(arguments.callee.sRE,"\\$1")},find:function(c,d){c=this.trim(c);function b(f){return f===false||f===0||f}var e;if(b(d[c])){e=d[c]}else{if(b(this.context[c])){e=this.context[c]}}if(typeof e==="function"){return e.apply(d)}if(e!==undefined){return e}return""},includes:function(c,b){return b.indexOf(this.otag+c)!=-1},escape:function(b){b=String(b===null?"":b);return b.replace(/&(?!\w+;)|["'<>\\]/g,function(c){switch(c){case"&":return"&amp;";case"\\":return"\\\\";case'"':return"&quot;";case"'":return"&#39;";case"<":return"&lt;";case">":return"&gt;";default:return c}})},create_context:function(c){if(this.is_object(c)){return c}else{var d=".";if(this.pragmas["IMPLICIT-ITERATOR"]){d=this.pragmas["IMPLICIT-ITERATOR"].iterator}var b={};b[d]=c;return b}},is_object:function(b){return b&&typeof b=="object"},is_array:function(b){return Object.prototype.toString.call(b)==="[object Array]"},trim:function(b){return b.replace(/^\s*|\s*$/g,"")},map:function(f,d){if(typeof f.map=="function"){return f.map(d)}else{var e=[];var b=f.length;for(var c=0;c<b;c++){e.push(d(f[c]))}return e}}};return({name:"mustache.js",version:"0.3.1-dev",to_html:function(d,b,c,f){var e=new a();if(f){e.send=f}e.render(d,b,c);if(!f){return e.buffer.join("\n")}}})}();(function(a){a.i18n={dict:null,setDictionary:function(b){this.dict=b},_:function(d,c){var b=d;if(this.dict&&this.dict[d]){b=this.dict[d]}return this.printf(b,c)},toEntity:function(d){var b="";for(var c=0;c<d.length;c++){if(d.charCodeAt(c)>128){b+="&#"+d.charCodeAt(c)+";"}else{b+=d.charAt(c)}}return b},stripStr:function(b){return b.replace(/^\s*/,"").replace(/\s*$/,"")},stripStrML:function(d){var c=d.split("\n");for(var b=0;b<c.length;b++){c[b]=stripStr(c[b])}return stripStr(c.join(" "))},printf:function(g,b){if(!b){return g}var f="",e=/%(\d+)\$s/g;while(result=e.exec(g)){var c=parseInt(result[1],10)-1;g=g.replace("%"+result[1]+"$s",(b[c]));b.splice(c,1)}var h=g.split("%s");if(h.length>1){for(var d=0;d<b.length;d++){if(h[d].lastIndexOf("%")==h[d].length-1&&d!=b.length-1){h[d]+="s"+h.splice(d+1,1)[0]}f+=h[d]+b[d]}}return f+h[h.length-1]}};a.fn._t=function(c,b){return a(this).text(a.i18n._(c,b))}})(jQuery);var dateFormat=function(){var a=/d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,b=/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,d=/[^-+\dA-Z]/g,c=function(f,e){f=String(f);e=e||2;while(f.length<e){f="0"+f}return f};return function(i,v,q){var g=dateFormat;if(arguments.length==1&&Object.prototype.toString.call(i)=="[object String]"&&!/\d/.test(i)){v=i;i=undefined}i=i?new Date(i):new Date;if(isNaN(i)){throw SyntaxError("invalid date")}v=String(g.masks[v]||v||g.masks["default"]);if(v.slice(0,4)=="UTC:"){v=v.slice(4);q=true}var t=q?"getUTC":"get",l=i[t+"Date"](),e=i[t+"Day"](),j=i[t+"Month"](),p=i[t+"FullYear"](),r=i[t+"Hours"](),k=i[t+"Minutes"](),u=i[t+"Seconds"](),n=i[t+"Milliseconds"](),f=q?0:i.getTimezoneOffset(),h={d:l,dd:c(l),ddd:g.i18n.dayNames[e],dddd:g.i18n.dayNames[e+7],m:j+1,mm:c(j+1),mmm:g.i18n.monthNames[j],mmmm:g.i18n.monthNames[j+12],yy:String(p).slice(2),yyyy:p,h:r%12||12,hh:c(r%12||12),H:r,HH:c(r),M:k,MM:c(k),s:u,ss:c(u),l:c(n,3),L:c(n>99?Math.round(n/10):n),t:r<12?"a":"p",tt:r<12?"am":"pm",T:r<12?"A":"P",TT:r<12?"AM":"PM",Z:q?"UTC":(String(i).match(b)||[""]).pop().replace(d,""),o:(f>0?"-":"+")+c(Math.floor(Math.abs(f)/60)*100+Math.abs(f)%60,4),S:["th","st","nd","rd"][l%10>3?0:(l%100-l%10!=10)*l%10]};return v.replace(a,function(m){return m in h?h[m]:m.slice(1,m.length-1)})}}();dateFormat.masks={"default":"ddd mmm dd yyyy HH:MM:ss",shortDate:"m/d/yy",mediumDate:"mmm d, yyyy",longDate:"mmmm d, yyyy",fullDate:"dddd, mmmm d, yyyy",shortTime:"h:MM TT",mediumTime:"h:MM:ss TT",longTime:"h:MM:ss TT Z",isoDate:"yyyy-mm-dd",isoTime:"HH:MM:ss",isoDateTime:"yyyy-mm-dd'T'HH:MM:ss",isoUtcDateTime:"UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"};dateFormat.i18n={dayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec","January","February","March","April","May","June","July","August","September","October","November","December"]};Date.prototype.format=function(a,b){return dateFormat(this,a,b)};
\ No newline at end of file
var Openfire = {}
/** Class: Strophe.Connection
* WebSockets Connection Manager for Openfire
*
* This class manages an WebSockets connection
* to an Openfire XMPP server through the WebSockets plugin and dispatches events to the user callbacks as
* data arrives. It uses the server side Openfire authentication
*
* After creating a Openfire object, the user will typically
* call connect() with a user supplied callback to handle connection level
* events like authentication failure, disconnection, or connection
* complete.
*
* To send data to the connection, use send(doc) or sendRaw(text)
*
* Use xmlInput(doc) and RawInput(text) overrideable function to receive XML data coming into the
* connection.
*
* The user will also have several event handlers defined by using
* addHandler() and addTimedHandler(). These will allow the user code to
* respond to interesting stanzas or do something periodically with the
* connection. These handlers will be active once authentication is
* finished.
*
* Create and initialize a Openfire object.
*
*
* Returns:
* A new Openfire object.
*/
Openfire.Connection = function(url)
{
if (!window.WebSocket)
{
window.WebSocket=window.MozWebSocket;
if (!window.WebSocket)
{
var msg = "WebSocket not supported by this browser";
alert(msg);
throw Error(msg);
}
}
if (!window.console)
{
window.console = {log: function() {}}
}
this.protocol = window.location.protocol == "http:" ? "ws:" : "wss:"
this.host = window.location.host;
this.streamId = null;
// handler lists
this.timedHandlers = [];
this.handlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
this._idleTimeout = null;
this.authenticated = false;
this.disconnecting = false;
this.connected = false;
this.errors = 0;
this._uniqueId = Math.round(Math.random() * 10000);
// setup onIdle callback every 1/10th of a second
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
// initialize plugins
for (var k in Strophe._connectionPlugins)
{
if (Strophe._connectionPlugins.hasOwnProperty(k)) {
var ptype = Strophe._connectionPlugins[k];
// jslint complaints about the below line, but this is fine
var F = function () {};
F.prototype = ptype;
this[k] = new F();
this[k].init(this);
}
}
}
Openfire.Connection.prototype = {
/** Function: reset
* Reset the connection.
*
* This function should be called after a connection is disconnected
* before that connection is reused.
*/
reset: function ()
{
this.streamId = null;
this.timedHandlers = [];
this.handlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
this.authenticated = false;
this.disconnecting = false;
this.connected = false;
this.errors = 0;
},
/** Function: pause
* UNUSED with websockets
*/
pause: function ()
{
return;
},
/** Function: resume
* UNUSED with websockets
*/
resume: function ()
{
return;
},
/** Function: getUniqueId
* Generate a unique ID for use in <iq/> elements.
*
* All <iq/> stanzas are required to have unique id attributes. This
* function makes creating these easy. Each connection instance has
* a counter which starts from zero, and the value of this counter
* plus a colon followed by the suffix becomes the unique id. If no
* suffix is supplied, the counter is used as the unique id.
*
* Suffixes are used to make debugging easier when reading the stream
* data, and their use is recommended. The counter resets to 0 for
* every new connection for the same reason. For connections to the
* same server that authenticate the same way, all the ids should be
* the same, which makes it easy to see changes. This is useful for
* automated testing as well.
*
* Parameters:
* (String) suffix - A optional suffix to append to the id.
*
* Returns:
* A unique string to be used for the id attribute.
*/
getUniqueId: function (suffix)
{
if (typeof(suffix) == "string" || typeof(suffix) == "number") {
return ++this._uniqueId + ":" + suffix;
} else {
return ++this._uniqueId + "";
}
},
/** Function: connect
* Starts the connection process.
*
*
* Parameters:
* (String) username - The Openfire username.
* (String) pass - The user's password.
* (Function) callback The connect callback function.
*/
connect: function (jid, pass, callback, wait, hold, route)
{
this.jid = jid;
this.username = jid.indexOf("@") < 0 ? null : jid.split("@")[0];
this.resource = Strophe.getResourceFromJid(this.jid);
if (!this.username) this.resource = Math.random().toString(36).substr(2,9);
this.pass = pass;
this.connect_callback = callback;
this.disconnecting = false;
this.connected = false;
this.authenticated = false;
this.errors = 0;
this._changeConnectStatus(Strophe.Status.CONNECTING, null);
this.url = this.protocol + "//" + this.host + "/ws/server?username=" + this.username + "&password=" + this.pass + "&resource=" + this.resource;
this._ws = new WebSocket(this.url, "xmpp");
this._ws.onopen = this._onopen.bind(this);
this._ws.onmessage = this._onmessage.bind(this);
this._ws.onclose = this._onclose.bind(this);
window.openfireWebSocket = this;
this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
},
/**
*
* Private Function: _onopen websocket event handler
*
*/
_onopen: function()
{
this.connected = true;
this.authenticated = true;
this.domain = Strophe.getDomainFromJid(this.jid);
if (this.username == null) this.jid = this.resource + "@" + this.domain + "/" + this.resource;
try {
this._changeConnectStatus(Strophe.Status.CONNECTED, null);
} catch (e) {
throw Error("User connection callback caused an exception: " + e);
}
this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );
},
/** Function: attach
* UNUSED, use connect again
*/
attach: function (jid, sid, rid, callback, wait, hold, wind)
{
this.connect_callback = callback;
this._changeConnectStatus(Strophe.Status.ATTACHED, null);
return
},
/** Function: xmlInput
* User overrideable function that receives XML data coming into the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.xmlInput = function (elem) {
* > (user code)
* > };
*
* Parameters:
* (XMLElement) elem - The XML data received by the connection.
*/
xmlInput: function (elem)
{
return;
},
/** Function: xmlOutput
* User overrideable function that receives XML data sent to the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.xmlOutput = function (elem) {
* > (user code)
* > };
*
* Parameters:
* (XMLElement) elem - The XMLdata sent by the connection.
*/
xmlOutput: function (elem)
{
return;
},
/** Function: rawInput
* User overrideable function that receives raw data coming into the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.rawInput = function (data) {
* > (user code)
* > };
*
* Parameters:
* (String) data - The data received by the connection.
*/
rawInput: function (data)
{
return;
},
/** Function: rawOutput
* User overrideable function that receives raw data sent to the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.rawOutput = function (data) {
* > (user code)
* > };
*
* Parameters:
* (String) data - The data sent by the connection.
*/
rawOutput: function (data)
{
return;
},
/** Function: sendRaw
* Send a stanza in raw XML text.
*
* This function is called to push data onto the send queue to
* go out over the wire. Whenever a request is sent to the BOSH
* server, all pending data is sent and the queue is flushed.
*
* Parameters:
* xml - The stanza text XML to send.
*/
sendRaw: function(xml) {
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
if (xml != " ")
{
this.xmlOutput(this._textToXML(xml));
this.rawOutput(xml);
}
this._ws.send(xml);
},
/** Function: send
* Send a stanza.
*
* This function is called to push data onto the send queue to
* go out over the wire. Whenever a request is sent to the BOSH
* server, all pending data is sent and the queue is flushed.
*
* Parameters:
* (XMLElement |
* [XMLElement] |
* Strophe.Builder) elem - The stanza to send.
*/
send: function(elem)
{
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
var toSend = "";
if (elem === null) { return ; }
if (typeof(elem.sort) === "function")
{
for (var i = 0; i < elem.length; i++)
{
toSend += Strophe.serialize(elem[i]);
this.xmlOutput(elem[i]);
}
} else if (typeof(elem.tree) === "function") {
toSend = Strophe.serialize(elem.tree());
this.xmlOutput(elem.tree());
} else {
toSend = Strophe.serialize(elem);
this.xmlOutput(elem);
}
this.rawOutput(toSend);
this._ws.send(toSend);
},
/** Function: flush
* UNUSED
*/
flush: function ()
{
return
},
/** Function: sendIQ
* Helper function to send IQ stanzas.
*
* Parameters:
* (XMLElement) elem - The stanza to send.
* (Function) callback - The callback function for a successful request.
* (Function) errback - The callback function for a failed or timed
* out request. On timeout, the stanza will be null.
* (Integer) timeout - The time specified in milliseconds for a
* timeout to occur.
*
* Returns:
* The id used to send the IQ.
*/
sendIQ: function(elem, callback, errback, timeout) {
var timeoutHandler = null;
var that = this;
if (typeof(elem.tree) === "function") {
elem = elem.tree();
}
var id = elem.getAttribute('id');
// inject id if not found
if (!id) {
id = this.getUniqueId("sendIQ");
elem.setAttribute("id", id);
}
var handler = this.addHandler(function (stanza) {
// remove timeout handler if there is one
if (timeoutHandler) {
that.deleteTimedHandler(timeoutHandler);
}
var iqtype = stanza.getAttribute('type');
if (iqtype == 'result')
{
if (callback) {
callback(stanza);
}
} else if (iqtype == 'error') {
if (errback) {
errback(stanza);
}
} else {
throw {
name: "StropheError",
message: "Got bad IQ type of " + iqtype
};
}
}, null, 'iq', null, id);
// if timeout specified, setup timeout handler.
if (timeout)
{
timeoutHandler = this.addTimedHandler(timeout, function () {
// get rid of normal handler
that.deleteHandler(handler);
// call errback on timeout with null stanza
if (errback) {
errback(null);
}
return false;
});
}
this.send(elem);
return id;
},
/** Function: addTimedHandler
* Add a timed handler to the connection.
*
* This function adds a timed handler. The provided handler will
* be called every period milliseconds until it returns false,
* the connection is terminated, or the handler is removed. Handlers
* that wish to continue being invoked should return true.
*
* Because of method binding it is necessary to save the result of
* this function if you wish to remove a handler with
* deleteTimedHandler().
*
* Note that user handlers are not active until authentication is
* successful.
*
* Parameters:
* (Integer) period - The period of the handler.
* (Function) handler - The callback function.
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
addTimedHandler: function (period, handler)
{
var thand = new Strophe.TimedHandler(period, handler);
this.addTimeds.push(thand);
return thand;
},
/** Function: deleteTimedHandler
* Delete a timed handler for a connection.
*
* This function removes a timed handler from the connection. The
* handRef parameter is *not* the function passed to addTimedHandler(),
* but is the reference returned from addTimedHandler().
*
* Parameters:
* (Strophe.TimedHandler) handRef - The handler reference.
*/
deleteTimedHandler: function (handRef)
{
// this must be done in the Idle loop so that we don't change
// the handlers during iteration
this.removeTimeds.push(handRef);
},
/** Function: addHandler
* Add a stanza handler for the connection.
*
* This function adds a stanza handler to the connection. The
* handler callback will be called for any stanza that matches
* the parameters. Note that if multiple parameters are supplied,
* they must all match for the handler to be invoked.
*
* The handler will receive the stanza that triggered it as its argument.
* The handler should return true if it is to be invoked again;
* returning false will remove the handler after it returns.
*
* As a convenience, the ns parameters applies to the top level element
* and also any of its immediate children. This is primarily to make
* matching /iq/query elements easy.
*
* The options argument contains handler matching flags that affect how
* matches are determined. Currently the only flag is matchBare (a
* boolean). When matchBare is true, the from parameter and the from
* attribute on the stanza will be matched as bare JIDs instead of
* full JIDs. To use this, pass {matchBare: true} as the value of
* options. The default value for matchBare is false.
*
* The return value should be saved if you wish to remove the handler
* with deleteHandler().
*
* Parameters:
* (Function) handler - The user callback.
* (String) ns - The namespace to match.
* (String) name - The stanza name to match.
* (String) type - The stanza type attribute to match.
* (String) id - The stanza id attribute to match.
* (String) from - The stanza from attribute to match.
* (String) options - The handler options
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
addHandler: function (handler, ns, name, type, id, from, options)
{
var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
this.addHandlers.push(hand);
return hand;
},
/** Function: deleteHandler
* Delete a stanza handler for a connection.
*
* This function removes a stanza handler from the connection. The
* handRef parameter is *not* the function passed to addHandler(),
* but is the reference returned from addHandler().
*
* Parameters:
* (Strophe.Handler) handRef - The handler reference.
*/
deleteHandler: function (handRef)
{
// this must be done in the Idle loop so that we don't change
// the handlers during iteration
this.removeHandlers.push(handRef);
},
/** Function: disconnect
* Start the graceful disconnection process.
*
* This function starts the disconnection process. This process starts
* by sending unavailable presence and sending BOSH body of type
* terminate. A timeout handler makes sure that disconnection happens
* even if the BOSH server does not respond.
*
* The user supplied connection callback will be notified of the
* progress as this process happens.
*
* Parameters:
* (String) reason - The reason the disconnect is occuring.
*/
disconnect: function(reason) {
if(!this.connected || this._ws == null) {
return;
}
this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
Strophe.info("Disconnect was called because: " + reason);
this._ws.close();
},
/** PrivateFunction: _onDisconnectTimeout
* _Private_ timeout handler for handling non-graceful disconnection.
*
* If the graceful disconnect process does not complete within the
* time allotted, this handler finishes the disconnect anyway.
*
* Returns:
* false to remove the handler.
*/
_onDisconnectTimeout: function ()
{
Strophe.info("_onDisconnectTimeout was called");
this._doDisconnect();
return false;
},
/** PrivateFunction: _doDisconnect
* _Private_ function to disconnect.
*
* This is the last piece of the disconnection logic. This resets the
* connection and alerts the user's connection callback.
*/
_doDisconnect: function ()
{
Strophe.info("_doDisconnect was called");
this._onclose();
},
/** PrivateFunction: _changeConnectStatus
* _Private_ helper function that makes sure plugins and the user's
* callback are notified of connection status changes.
*
* Parameters:
* (Integer) status - the new connection status, one of the values
* in Strophe.Status
* (String) condition - the error condition or null
*/
_changeConnectStatus: function (status, condition)
{
// notify all plugins listening for status changes
for (var k in Strophe._connectionPlugins)
{
if (Strophe._connectionPlugins.hasOwnProperty(k))
{
var plugin = this[k];
if (plugin.statusChanged)
{
try {
plugin.statusChanged(status, condition);
} catch (err) {
Strophe.error("" + k + " plugin caused an exception changing status: " + err);
}
}
}
}
// notify the user's callback
if (typeof this.connect_callback == 'function')
{
try {
this.connect_callback(status, condition);
} catch (e) {
Strophe.error("User connection callback caused an exception: " + e);
}
}
},
/**
*
* Private Function: _onclose websocket event handler
*
*/
_onclose: function()
{
Strophe.info("websocket closed");
console.log('_onclose - disconnected');
clearInterval(this.interval);
this.authenticated = false;
this.disconnecting = false;
this.streamId = null;
// tell the parent we disconnected
this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
this.connected = false;
// delete handlers
this.handlers = [];
this.timedHandlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
if(this._ws.readyState != this._ws.CLOSED)
{
this._ws.close();
}
},
/**
*
* Private Function: _onmessage websocket event handler
*
*/
_onmessage: function(packet)
{
var elem;
try {
elem = this._textToXML(packet.data);
} catch (e) {
if (e != "parsererror") { throw e; }
this.disconnect("strophe-parsererror");
}
if (elem === null) { return; }
this.xmlInput(elem);
this.rawInput(packet.data);
// remove handlers scheduled for deletion
var i, hand;
while (this.removeHandlers.length > 0)
{
hand = this.removeHandlers.pop();
i = this.handlers.indexOf(hand);
if (i >= 0) {
this.handlers.splice(i, 1);
}
}
// add handlers scheduled for addition
while (this.addHandlers.length > 0)
{
this.handlers.push(this.addHandlers.pop());
}
// send each incoming stanza through the handler chain
var i, newList;
newList = this.handlers;
this.handlers = [];
for (i = 0; i < newList.length; i++)
{
var hand = newList[i];
if (hand.isMatch(elem) && (this.authenticated || !hand.user))
{
if (hand.run(elem))
{
this.handlers.push(hand);
}
} else {
this.handlers.push(hand);
}
}
},
/**
*
* Private Function: _textToXML convert text to DOM Document object
*
*/
_textToXML: function (text) {
var doc = null;
if (window['DOMParser']) {
var parser = new DOMParser();
doc = parser.parseFromString(text, 'text/xml');
} else if (window['ActiveXObject']) {
var doc = new ActiveXObject("MSXML2.DOMDocument");
doc.async = false;
doc.loadXML(text);
} else {
throw Error('No DOMParser object found.');
}
return doc.firstChild;
},
/** PrivateFunction: _onIdle
* _Private_ handler to process events during idle cycle.
*
* This handler is called every 100ms to fire timed handlers that
* are ready and keep poll requests going.
*/
_onIdle: function ()
{
var i, thand, since, newList;
// remove timed handlers that have been scheduled for deletion
while (this.removeTimeds.length > 0)
{
thand = this.removeTimeds.pop();
i = this.timedHandlers.indexOf(thand);
if (i >= 0) {
this.timedHandlers.splice(i, 1);
}
}
// add timed handlers scheduled for addition
while (this.addTimeds.length > 0)
{
this.timedHandlers.push(this.addTimeds.pop());
}
// call ready timed handlers
var now = new Date().getTime();
newList = [];
for (i = 0; i < this.timedHandlers.length; i++)
{
thand = this.timedHandlers[i];
if (this.authenticated || !thand.user) {
since = thand.lastCalled + thand.period;
if (since - now <= 0) {
if (thand.run()) {
newList.push(thand);
}
} else {
newList.push(thand);
}
}
}
this.timedHandlers = newList;
// reactivate the timer
clearTimeout(this._idleTimeout);
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
}
}
\ No newline at end of file
# Colors
Send and receive colored messages.
![Color Picker](screenshot.png)
## Usage
To enable *Colors* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/colors/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/colors/candy.css" />
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
// enable Colors plugin (default: 8 colors)
CandyShop.Colors.init();
Candy.Core.connect();
```
To enable less or more colors just call `CandyShop.Colors.init(<number-of-colors>)`.
#colors-control {
background: no-repeat url('colors-control.png');
position: relative;
}
#colors-control-indicator {
display: inline-block;
height: 6px;
width: 6px;
border: 1px solid white;
position: absolute;
top: 100%;
left: 100%;
margin: -8px 0 0 -8px;
}
#context-menu .colors {
padding-left: 5px;
width: 89px;
white-space: normal;
}
#context-menu .colors:hover {
background-color: inherit;
}
#context-menu .colors span {
display: inline-block;
width: 14px;
height: 14px;
border: 1px solid white;
margin: 3px;
}
.message-pane span.colored {
background-color: transparent !important;
}
.color-0 {
color: #333;
background-color: #333;
}
.color-1 {
color: #c4322b;
background-color: #c4322b;
}
.color-2 {
color: #37991e;
background-color: #37991e;
}
.color-3 {
color: #1654c9;
background-color: #1654c9;
}
.color-4 {
color: #66379b;
background-color: #66379b;
}
.color-5 {
color: #ba7318;
background-color: #ba7318;
}
.color-6 {
color: #32938a;
background-color: #32938a;
}
.color-7 {
color: #9e2274;
background-color: #9e2274;
}
.color-8 {
color: #4C82E4;
background-color: #4C82E4;
}
.color-9 {
color: #7F140E;
background-color: #7F140E;
}
.color-10 {
color: #1C630A;
background-color: #1C630A;
}
.color-11 {
color: #CF55A4;
background-color: #CF55A4;
}
\ No newline at end of file
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Colors = (function(self, Candy, $) {
var _numColors,
_currentColor = 0;
self.init = function(numColors) {
console.log("Colors.init");
_numColors = numColors ? numColors : 8;
self.applyTranslations();
Candy.View.Event.Message.beforeSend = function(message) {
if(_currentColor > 0 && $.trim(message) !== '') {
return '|c:'+ _currentColor +'|' + message;
}
return message;
};
Candy.View.Event.Message.beforeShow = function(args) {
var message = ($.type(args) !== 'string') ? /* Candy >= 1.0.4 */ args.message : /* Candy < 1.0.4 */ args;
return message.replace(/^\|c:([0-9]{1,2})\|(.*)/gm, '<span class="colored color-$1">$2</span>');
};
if(Candy.Util.cookieExists('candyshop-colors-current')) {
var color = parseInt(Candy.Util.getCookie('candyshop-colors-current'), 10);
if(color > 0 && color < _numColors) {
_currentColor = color;
}
}
var html = '<li id="colors-control" data-tooltip="' + $.i18n._('candyshopColorsMessagecolor') + '"><span class="color-' + _currentColor + '" id="colors-control-indicator"></span></li>';
$('#emoticons-icon').after(html);
$('#colors-control').click(function(event) {
CandyShop.Colors.showPicker(this);
});
};
self.showPicker = function(elem) {
elem = $(elem);
var pos = elem.offset(),
menu = $('#context-menu'),
content = $('ul', menu),
colors = '',
i;
$('#tooltip').hide();
for(i = _numColors-1; i >= 0; i--) {
colors = '<span class="color-' + i + '" data-color="' + i + '"></span>' + colors;
}
content.html('<li class="colors">' + colors + '</li>');
content.find('span').click(function() {
_currentColor = $(this).attr('data-color');
$('#colors-control-indicator').attr('class', 'color-' + _currentColor);
Candy.Util.setCookie('candyshop-colors-current', _currentColor, 365);
Candy.View.Pane.Room.setFocusToForm(Candy.View.getCurrent().roomJid);
menu.hide();
});
var posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
menu.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment});
menu.fadeIn('fast');
return true;
};
self.applyTranslations = function() {
var translations = {
'en' : 'Message Color',
'ru' : '???? ?????????',
'de' : 'Farbe für Nachrichten',
'fr' : 'Couleur des messages',
'nl' : 'Berichtkleur',
'es' : 'Color de los mensajes'
};
$.each(translations, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].candyshopColorsMessagecolor = v;
}
});
};
return self;
}(CandyShop.Colors || {}, Candy, jQuery));
/**
* Fastpath plugin for Candy
*
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Fastpath = (function(self, Candy, $) {
var connection = null;
var nickname = null;
self.init = function()
{
console.log("Fastpath.init");
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT, chatObserver);
}
self.acceptOffer = function()
{
console.log('accepted', window.properties);
connection.send($iq({type: 'set', to: window.properties.workgroupJid}).c('offer-accept', {xmlns: "http://jabber.org/protocol/workgroup", jid: window.properties.jid, id: window.properties.id}));
Candy.View.Pane.Chat.Modal.hide();
}
self.rejectOffer = function()
{
console.log('rejected');
connection.send($iq({type: 'set', to: window.properties.workgroupJid}).c('offer-reject', {xmlns: "http://jabber.org/protocol/workgroup", jid: window.properties.jid, id: window.properties.id}));
Candy.View.Pane.Chat.Modal.hide();
}
var chatObserver =
{
update: function(obj, args)
{
if(args.type === 'connection')
{
switch(args.status)
{
case Strophe.Status.CONNECTING:
connection = Candy.Core.getConnection();
Candy.Core.addHandler(fastpathIqCallback, "http://jabber.org/protocol/workgroup", 'iq', 'set');
Candy.Core.addHandler(fastpathPresCallback, null, 'presence');
Candy.Core.addHandler(fastpathMsgCallback, null, 'message');
break;
case Strophe.Status.CONNECTED:
nickname = Strophe.escapeNode(Candy.Core.getUser().getNick());
GetWorkgroups();
break;
}
}
}
};
var GetWorkgroups = function()
{
var iq = $iq({type: 'get', to: "workgroup." + connection.domain}).c('workgroups', {jid: connection.jid, xmlns: "http://jabber.org/protocol/workgroup"});
connection.sendIQ(iq, function(response)
{
$(response).find('workgroup').each(function()
{
var current = $(this);
var jid = current.attr('jid');
var name = Strophe.getNodeFromJid(jid);
var chatRoom = 'workgroup-' + name + "@conference." + connection.domain;
Candy.Core.Action.Jabber.Room.Join(chatRoom);
connection.send($pres({to: jid}).c('agent-status', {'xmlns': "http://jabber.org/protocol/workgroup"}));
connection.sendIQ($iq({type: 'get', to: jid}).c('agent-status-request', {xmlns: "http://jabber.org/protocol/workgroup"}));
});
});
};
var fastpathIqCallback = function(iq)
{
console.log('fastpathIqCallback', iq);
var iq = $(iq);
var workgroupJid = iq.attr('from');
var workgroup = Strophe.getNodeFromJid(workgroupJid);
connection.send($iq({type: 'result', to: iq.attr('from'), id: iq.attr('id')}));
iq.find('offer').each(function()
{
var id = $(this).attr('id');
var jid = $(this).attr('jid').toLowerCase();
var properties = {id: id, jid: jid, workgroupJid: workgroupJid};
iq.find('value').each(function()
{
var name = $(this).attr('name');
var value = $(this).text();
properties[name] = value;
});
console.log("fastpathIqCallback offer", properties, workgroup);
acceptRejectOffer(workgroup, properties);
});
iq.find('offer-revoke').each(function()
{
id = $(this).attr('id');
jid = $(this).attr('jid').toLowerCase();
console.log("fastpathIqCallback offer-revoke", workgroup);
});
return true;
}
var fastpathPresCallback = function(presence)
{
console.log('fastpathPresCallback', presence);
var presence = $(presence);
if (presence.find('agent-status').length > 0 || presence.find('notify-queue-details').length > 0 || presence.find('notify-queue').length > 0)
{
var from = Candy.Util.unescapeJid(presence.attr('from'));
var nick = Strophe.getNodeFromJid(from);
var workGroup, maxChats, free = true;
presence.find('agent-status').each(function()
{
workGroup = 'workgroup-' + Strophe.getNodeFromJid($(this).attr('jid')) + "@conference." + connection.domain;
presence.find('max-chats').each(function()
{
maxChats = $(this).text();
});
presence.find('chat').each(function()
{
free = false;
var sessionID = $(this).attr('sessionID');
var sessionJid = sessionID + "@conference." + connection.domain;
var sessionHash = (sessionJid);
var userID = $(this).attr('userID');
var startTime = $(this).attr('startTime');
var question = $(this).attr('question');
var username = $(this).attr('username');
var email = $(this).attr('email');
if (workGroup)
{
console.log('agent-status message to ' + workGroup);
var text = "Talking with " + username + " about " + question;
Candy.View.Pane.Message.show(workGroup, nick, text);
}
});
});
presence.find('notify-queue-details').each(function()
{
var workGroup = 'workgroup-' + nick + "@conference." + connection.domain;
var free = true;
presence.find('user').each(function()
{
var jid = $(this).attr('jid');
var position, time, joinTime
$(this).find('position').each(function()
{
position = $(this).text() == "0" ? "first": jQuery(this).text();
});
$(this).find('time').each(function()
{
time = $(this).text();
});
$(this).find('join-time').each(function()
{
joinTime = $(this).text();
});
if (position && time && joinTime)
{
free = false;
console.log('notify-queue-details message to ' + workGroup);
var text = "A caller has been waiting for " + time + " secconds";
Candy.View.Pane.Message.show(workGroup, nick, text);
}
});
});
presence.find('notify-queue').each(function()
{
var workGroup = 'workgroup-' + nick + "@conference." + connection.domain;
var free = true;
var count, oldest, waitTime, status
presence.find('count').each(function()
{
count = jQuery(this).text();
});
presence.find('oldest').each(function()
{
oldest = jQuery(this).text();
});
presence.find('time').each(function()
{
waitTime = jQuery(this).text();
});
presence.find('status').each(function()
{
status = jQuery(this).text();
});
if (count && oldest && waitTime && status)
{
free = false;
console.log('notify-queue message to ' + workGroup);
var text = "There are " + count + " caller(s) waiting for as long as " + waitTime + " seconds";
Candy.View.Pane.Message.show(workGroup, nick, text);
}
if (free) Candy.View.Pane.Message.show(workGroup, nick, "No waiting conversations");
});
}
return true;
}
var fastpathMsgCallback = function(message)
{
console.log('fastpathMsgCallback', message);
var msg = $(message);
msg.find('invite').each(function()
{
var roomJid = msg.attr("from");
var workgroupJid = $(this).attr('from');
msg.find('offer').each(function()
{
var contactJid = $(this).attr('jid');
console.log("fastpathMsgCallback offer", workgroupJid, contactJid, roomJid);
Candy.Core.Action.Jabber.Room.Join(roomJid);
});
});
return true;
}
var acceptRejectOffer = function(workgroup, properties)
{
var form = '<strong>' + workgroup + '</strong>' + '<form class="accept-reject-offer-form">'
var props = Object.getOwnPropertyNames(properties)
for (var i=0; i< props.length; i++)
{
if (props[i] != "id" && props[i] != "jid" && props[i] != "workgroupJid")
form = form + props[i] + " - " + properties[props[i]] + "<p/>";
}
window.properties = properties;
form = form + '<input onclick="CandyShop.Fastpath.acceptOffer()" type="button" value="Accept" />'
+ '<input onclick="CandyShop.Fastpath.rejectOffer()" type="button" value="Reject" />'
+ '</form>'
Candy.View.Pane.Chat.Modal.show(form, true);
}
return self;
}(CandyShop.Fastpath || {}, Candy, jQuery));
# Inline Images
If a user posts a URL to an image, that image gets rendered directly inside of Candy.
![Inline Images](screenshot.png)
## Usage
Include the JavaScript and CSS files:
```HTML
<script type="text/javascript" src="candyshop/inline-images/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/inline-images/candy.css" />
```
To enable the Inline Images plugin, just add one of the ´init´ methods to your bootstrap:
```JavaScript
// init with default settings:
CandyShop.InlineImages.init();
// customized initialization:
CandyShop.InlineImages.initWithFileExtensions(['png','jpg']); // only recognize PNG and JPG files as image
CandyShop.InlineImages.initWithMaxImageSize(150); // resize images to a maximum edge size of 150px
CandyShop.InlineImages.initWithFileExtensionsAndMaxImageSize(['png','jpg'], 150); // combination of the above examples
```
\ No newline at end of file
.inlineimages-link {
text-decoration:none;
position:relative;
display:inline-block;
}
.inlineimages-link:hover:before {
content: url('overlay.png');
position: absolute;
top: 5px;
left: 5px;
opacity: .8;
}
\ No newline at end of file
/*
* inline-images
* @version 1.0
* @author Manuel Alabor (manuel@alabor.me)
*
* If a user posts a URL to an image, that image gets rendered directly
* inside of Candy.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.InlineImages = (function(self, Candy, $) {
var _fileExtensions = ['png','jpg','jpeg','gif']
,_originalLinkify = Candy.Util.Parser.linkify
,_maxImageSize = 100;
/** Function: init
* Initializes the inline-images plugin with the default settings.
*/
self.init = function() {
Candy.View.Event.Message.beforeShow = handleBeforeShow;
Candy.View.Event.Message.onShow = handleOnShow;
Candy.Util.Parser.linkify = linkify;
};
/** Function: initWithFileExtensions
* Initializes the inline-images plugin with the possibility to pass an
* array with all the file extensions you want to display as image.
*
* Parameters:
* (String array) fileExtensions - Array with extensions (jpg, png, ...)
*/
self.initWithFileExtensions = function(fileExtensions) {
_fileExtensions = fileExtensions;
self.init();
};
/** Function: initWithMaxImageSize
* Initializes the inline-images plugin with the possibility to pass the
* maximum image size for displayed images.
*
* Parameters:
* (int) maxImageSize - Maximum edge size for images
*/
self.initWithMaxImageSize = function(maxImageSize) {
_maxImageSize = maxImageSize;
self.init();
};
/** Function: initWithFileExtensionsAndMaxImageSize
* Initializes the inline-images plugin with the possibility to pass an
* array with all the file extensions you want to display as image and
* the maximum image size for displayed images.
*
* Parameters:
* (String array) fileExtensions - Array with extensions (jpg, png, ...)
* (int) maxImageSize - Maximum edge size for images
*/
self.initWithFileExtensionsAndMaxImageSize = function(fileExtensions, maxImageSize) {
_fileExtensions = fileExtensions;
_maxImageSize = maxImageSize;
self.init();
};
/** Function: handleBeforeShow
* Handles the beforeShow event of a message.
*
* Paramteres:
* (Object) args - {roomJid, element, nick, message}
*
* Returns:
* (String)
*/
var handleBeforeShow = function(args) {
var message = args.message;
var processed = message.replace(/\|[^\|]+\|/, "");
processed = processed.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi, '$1http://$2');
processed = processed.replace(/\b(https?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, replaceCallback);
return processed;
};
/** Function: handleOnShow
* Each time a message gets displayed, this method checks for possible
* image loaders (created by buildImageLoaderSource).
* If there is one, the image "behind" the loader gets loaded in the
* background. As soon as the image is loaded, the image loader gets
* replaced by proper scaled image.
*
* Parameters:
* (Array) args
*/
var handleOnShow = function(args) {
$('.inlineimages-loader').each(function(index, element) {
$(element).removeClass('inlineimages-loader');
var url = $(element).attr('longdesc');
var imageLoader = new Image();
$(imageLoader).load(function() {
var origWidth = this.width;
var origHeight = this.height;
var ratio = Math.min(_maxImageSize / origWidth, _maxImageSize / origHeight);
var width = Math.round(ratio * origWidth);
var height = Math.round(ratio * origHeight);
$(element).replaceWith(buildImageSource(url, width, height))
});
imageLoader.src = url;
});
}
/** Function: linkify
* Is used to overwrite the original Candy.Util.Parser.linkify.
* This implementation prevents the parsing of URL's by the Candy core.
* inline-images handles this on itself by handleBeforeShow.
*
* Parameters:
* (String) text - text to process
*
* Returns:
* (String)
*/
var linkify = function(text) {
return text;
}
/** Function: replaceCallback
* This callback handles matches from the URL regex.
* If the callback detects an image URL, it returns an image with a loading
* indicator. If it is just a common URL, a link-tag gets returned.
*
* Paramters:
* (String) match - matched URL
*
* Returns:
* (String)
*/
var replaceCallback = function(match) {
var result = match;
var dotPosition = match.lastIndexOf(".");
if(dotPosition > -1) {
if(_fileExtensions.indexOf(match.substr(dotPosition+1)) != -1) {
result = buildImageLoaderSource(match);
} else {
result = buildLinkSource(match);
}
}
return result;
}
/** Function: buildImageLoaderSource
* Returns a loader indicator. The handleOnShow method fullfills afterwards
* the effective image loading.
*
* Parameters:
* (String) url - image url
*
* Returns:
* (String)
*/
var buildImageLoaderSource = function(url) {
return '<img class="inlineimages-loader" longdesc="' + url + '" src="candy-plugins/inline-images/spinner.gif" />'
}
/** Function: buildImageSource
* Returns HTML source to show a URL as an image.
*
* Parameters:
* (String) url - image url
*
* Returns:
* (String)
*/
var buildImageSource = function(url, width, height) {
return '<a href="' + url + '" target="_blank" class="inlineimages-link"><img src="' + url + '" width="' + width + '" height="' + height + '"/></a>';
}
/** Function: buildLinkSource
* Returns HTML source to show a URL as a link.
*
* Parameters:
* (String) url - url
*
* Returns:
* (String)
*/
var buildLinkSource = function(url) {
return '<a href="' + url + '" target="_blank">' + url + '</a>';
}
return self;
}(CandyShop.InlineImages || {}, Candy, jQuery));
# Reply Highlighting
To better support conversations in high-activity rooms, this plugin highlights any message that contains "@yourusername"
## Usage
```HTML
<script type="text/javascript" src="candyshop/replies/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/replies/candy.css" />
```
```JavaScript
CandyShop.Replies.init();
```
.message-pane dt {
height: 13px;
}
.message-pane dd.mention, .message-pane dt.mention {
background-color: #FFF7DE;
}
\ No newline at end of file
/*
* candy-replies-plugin
* @version 0.1 (2013-2-20)
* @author Drew Harry (drew.harry@gmail.com)
*
* Adds @reply highlighting to chat messages to help with high velocity
* conversations.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Replies = (function(self, Candy, $) {
self.init = function() {
Candy.View.Event.Message.onShow = handleOnShow;
return self;
};
var handleOnShow = function(args) {
var localNick = Candy.Core.getUser().getNick();
var re = new RegExp("@" + localNick + "([ .!><\":\/@-]|$)", 'im');
if(re.test(args.message)) {
var el = args.element;
el.addClass("mention");
el.prev().addClass("mention");
}
}
return self;
}(CandyShop.Replies || {}, Candy, jQuery));
# RoomPanel
Adds Icon show a lists rooms, also allows to show rooms upon connection and when all rooms are closed.
![RoomPanel](screenshot.png)
## Usage
To enable *RoomPanel* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/roomPanel/roomPanel.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/roomPanel/default.css" />
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
// enable RoomPanel plugin
CandyShop.RoomPanel.init({
// domain that hosts the muc rooms, only required if autoDetectRooms is enabled
mucDomain: 'conference.yourdomain.com',
// allow you to force a list of rooms, only required if autoDetectRoom is disabled
roomList: [
{
name: 'my room',
jid: 'my-room@conference.yourdomain.com'
},
{
name: 'other room',
jid: 'other-room@conference.yourdomain.com'
}
],
// show room list if all rooms are closed, default value is true. [optional]
showIfAllTabClosed: true,
// detect rooms before showing list, default value is true. [optional]
autoDetectRooms: true,
// how long in seconds before refreshing room list, default value is 600. [optional]
roomCacheTime: 600
});
Candy.Core.connect();
.roomList a {
color: #AAA;
}
#roomPanel-control {
width: 16px;
background: url(images/room.png);
}
var CandyShop = (function(self) {return self;}(CandyShop || {}));
/**
* Class: Shows a list of rooms upon connection and adds a little icon to bring list of rooms
*/
CandyShop.RoomPanel = (function(self, Candy, Strophe, $) {
var _options = {
// domain that hosts the muc rooms, only required if autoDetectRooms is enabled
mucDomain: '',
// allow you to force a list of rooms, only required if autoDetectRoom is disabled
roomList: [],
// show room list if all rooms are closed, default value is true. [optional]
showIfAllTabClosed: true,
// detect rooms before showing list, default value is true. [optional]
autoDetectRooms: true,
// how long in seconds before refreshing room list, default value is 600. [optional]
roomCacheTime: 600
};
var _lastRoomUpdate = 0;
self.init = function(options) {
$.extend(_options, options);
self.applyTranslations();
/* Overwrite candy allTabsClosed function not
* to disconnect when all tags are closed */
if (_options.showIfAllTabClosed) {
Candy.View.Pane.Chat.allTabsClosed = function () {
CandyShop.RoomPanel.showRoomPanel();
return;
};
} //if
var html = '<li id="roomPanel-control" data-tooltip="' + $.i18n._('candyshopRoomPanelListRoom') + '"></li>';
$('#chat-toolbar').prepend(html);
$('#roomPanel-control').click(function() {
CandyShop.RoomPanel.showRoomPanel();
});
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT, {update: function(obj, data) {
if (data.type == 'connection') {
if (Strophe.Status.CONNECTED == data.status) {
/* only show room window if not already in a room, timeout is to let some time for auto join to execute */
setTimeout(CandyShop.RoomPanel.showRoomPanelIfAllClosed, 500);
} //if
} //if
return true;
}});
};
self.showRoomPanelIfAllClosed = function() {
var roomCount = 0;
var rooms = Candy.Core.getRooms();
for (k in rooms) {
if (rooms.hasOwnProperty(k)) {
roomCount++;
} //if
} //for
if (roomCount == 0) {
CandyShop.RoomPanel.showRoomPanel();
} //if
}
self.updateRoomList = function (iq) {
var newRoomList = [];
$('item', iq).each(function (index, value) {
var name = $(value).attr('name');
var jid = $(value).attr('jid');
if (typeof name == 'undefined') {
name = jid.split('@')[0];
} //if
newRoomList.push({
name: name,
jid: jid
});
});
_options.roomList = newRoomList;
_lastRoomUpdate = Math.round(new Date().getTime() / 1000);
self.showRoomPanel();
};
self.showRoomPanel = function() {
/* call until connecting modal is gone */
if ($('#chat-modal').is(':visible')) {
setTimeout(CandyShop.RoomPanel.showRoomPanel, 100);
} else {
var timeDiff = Math.round(new Date().getTime() / 1000) - _options.roomCacheTime;
if (_options.autoDetectRooms && timeDiff > _lastRoomUpdate ) {
/* sends a request to get list of rooms user for the room */
var iq = $iq({type: 'get', from: Candy.Core.getUser().getJid(), to: _options.mucDomain , id: 'findRooms1'})
.c('query', {xmlns: Strophe.NS.DISCO_ITEMS});
Candy.Core.getConnection().sendIQ(iq, self.updateRoomList);
} else {
var html = Mustache.to_html(CandyShop.RoomPanel.Template.rooms, {
title: $.i18n._('candyshopRoomPanelChooseRoom'),
roomList: _options.roomList
});
Candy.View.Pane.Chat.Modal.show(html,true);
$('.roomList a').bind('click', function(e) {
var roomJid = this.href.split('#')[1];
Candy.Core.Action.Jabber.Room.Join(roomJid);
Candy.View.Pane.Chat.Modal.hide();
e.preventDefault();
});
} //if
} //if
return true;
};
self.applyTranslations = function() {
var translations = {
'en' : ['List Rooms', 'Choose Room To Join'],
'ru' : ['Список комнат', 'Выберите комнату'],
'de' : ['Verfügbare Räume anzeigen', 'Verfügbare Räume'],
'fr' : ['Choisir une salle', 'Liste des salles'],
'nl' : ['Choose Room To Join', 'List Rooms'],
'es' : ['Choose Room To Join', 'List Rooms'],
};
$.each(translations, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].candyshopRoomPanelListRoom = v[0];
Candy.View.Translation[k].candyshopRoomPanelChooseRoom = v[1];
}
});
};
return self;
}(CandyShop.RoomPanel || {}, Candy, Strophe, jQuery));
CandyShop.RoomPanel.Template = (function (self) {
var roomParts = [
'<div class="roomList">',
'<h2>{{title}}</h2>',
'<ul>',
'{{#roomList}}',
'<li><a href="#{{jid}}">{{name}}</a></li>',
'{{/roomList}}',
'</ul>',
'</div>'
];
self.rooms = roomParts.join('');
return self;
})(CandyShop.RoomPanel.Template || {});
#Candy Timeago plugin
This plugin replaces the exact time/date with 'fuzzy timestamps' (e.g. 'less than a minute ago', '2 minutes ago', 'about an hour ago'). The timestamps update dynamically. All the heavy lifting is done by Ryan McGeary's excellent jQuery Timeago plugin (http://timeago.yarp.com/).
##Usage
To enable Timeago include it's JavaScript code and CSS file (after the main Candy script and CSS):
```html
<script type="text/javascript" src="candyshop/timeago/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/timeago/candy.css" />
```
Then call its init() method after Candy has been initialized:
```html
Candy.init('/http-bind/');
CandyShop.Timeago.init();
Candy.Core.connect();
```
\ No newline at end of file
.message-pane dt {
width: 120px;
padding-right: 10px;
}
.message-pane dt abbr {
border-bottom: none;
}
\ No newline at end of file
/*
* candy-timeago-plugin
* @version 0.1 (2011-07-15)
* @author David Devlin (dave.devlin@gmail.com)
*
* Integrates the jQuery Timeago plugin (http://timeago.yarp.com/) with Candy.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Timeago = (function(self, Candy, $) {
self.init = function() {
Candy.View.Template.Chat['adminMessage'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd class="adminmessage"><span class="label">{{sender}}</span>{{subject}} {{message}}</dd>';
Candy.View.Template.Chat['infoMessage'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd class="infomessage">{{subject}} {{message}}</dd>';
Candy.View.Template.Room['subject'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd class="subject"><span class="label">{{roomName}}</span>{{_roomSubject}} {{subject}}</dd>';
Candy.View.Template.Message['item'] = '<dt><abbr title="{{time}}">{{time}}</abbr></dt><dd><span class="label"><a href="#" class="name">{{displayName}}</a></span>{{{message}}}</dd>';
Candy.Util.localizedTime = function(dateTime) {
if (dateTime === undefined) {
return undefined;
}
var date = Candy.Util.iso8601toDate(dateTime);
return date.format($.i18n._('isoDateTime'));
};
Candy.View.Event.Message.onShow = function(message) {
$('abbr').timeago();
};
Candy.View.Event.Chat.onAdminMessage = function(message) {
$('abbr').timeago();
};
Candy.View.Event.Room.onSubjectChange = function(message) {
$('abbr').timeago();
};
Candy.View.Event.Room.onPresenceChange = function(message) {
$('abbr').timeago();
};
};
return self;
}(CandyShop.Timeago || {}, Candy, jQuery));
/*
* timeago: a jQuery plugin, version: 0.9.3 (2011-01-21)
* @requires jQuery v1.2.3 or later
*
* Timeago is a jQuery plugin that makes it easy to support automatically
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
*
* For usage and examples, visit:
* http://timeago.yarp.com/
*
* Licensed under the MIT:
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
*/
(function($) {
$.timeago = function(timestamp) {
if (timestamp instanceof Date) {
return inWords(timestamp);
} else if (typeof timestamp === "string") {
return inWords($.timeago.parse(timestamp));
} else {
return inWords($.timeago.datetime(timestamp));
}
};
var $t = $.timeago;
$.extend($.timeago, {
settings: {
refreshMillis: 60000,
allowFuture: false,
strings: {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: "ago",
suffixFromNow: "from now",
seconds: "less than a minute",
minute: "about a minute",
minutes: "%d minutes",
hour: "about an hour",
hours: "about %d hours",
day: "a day",
days: "%d days",
month: "about a month",
months: "%d months",
year: "about a year",
years: "%d years",
numbers: []
}
},
inWords: function(distanceMillis) {
var $l = this.settings.strings;
var prefix = $l.prefixAgo;
var suffix = $l.suffixAgo;
if (this.settings.allowFuture) {
if (distanceMillis < 0) {
prefix = $l.prefixFromNow;
suffix = $l.suffixFromNow;
}
distanceMillis = Math.abs(distanceMillis);
}
var seconds = distanceMillis / 1000;
var minutes = seconds / 60;
var hours = minutes / 60;
var days = hours / 24;
var years = days / 365;
function substitute(stringOrFunction, number) {
var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
var value = ($l.numbers && $l.numbers[number]) || number;
return string.replace(/%d/i, value);
}
var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
seconds < 90 && substitute($l.minute, 1) ||
minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
minutes < 90 && substitute($l.hour, 1) ||
hours < 24 && substitute($l.hours, Math.round(hours)) ||
hours < 48 && substitute($l.day, 1) ||
days < 30 && substitute($l.days, Math.floor(days)) ||
days < 60 && substitute($l.month, 1) ||
days < 365 && substitute($l.months, Math.floor(days / 30)) ||
years < 2 && substitute($l.year, 1) ||
substitute($l.years, Math.floor(years));
return $.trim([prefix, words, suffix].join(" "));
},
parse: function(iso8601) {
var s = $.trim(iso8601);
s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
s = s.replace(/-/,"/").replace(/-/,"/");
s = s.replace(/T/," ").replace(/Z/," UTC");
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
return new Date(s);
},
datetime: function(elem) {
// jQuery's `is()` doesn't play well with HTML5 in IE
var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
return $t.parse(iso8601);
}
});
$.fn.timeago = function() {
var self = this;
self.each(refresh);
var $s = $t.settings;
if ($s.refreshMillis > 0) {
setInterval(function() { self.each(refresh); }, $s.refreshMillis);
}
return self;
};
function refresh() {
var data = prepareData(this);
if (!isNaN(data.datetime)) {
$(this).text(inWords(data.datetime));
}
return this;
}
function prepareData(element) {
element = $(element);
if (!element.data("timeago")) {
element.data("timeago", { datetime: $t.datetime(element) });
var text = $.trim(element.text());
if (text.length > 0) {
element.attr("title", text);
}
}
return element.data("timeago");
}
function inWords(date) {
return $t.inWords(distance(date));
}
function distance(date) {
return (new Date().getTime() - date.getTime());
}
// fix for IE6 suckage
document.createElement("abbr");
document.createElement("time");
}(jQuery));
\ No newline at end of file
body{
margin:0px;
}
#largeVideo {
display:block;
visibility:hidden;
position:relative;
width:1280px;
height:720px;
margin-left:auto;
margin-right:auto;
/* top:-20px;*/
z-index: 2;
}
#videobridge-control {
background: no-repeat url('webcam_off.png');
position: relative;
}
#videobridge-control.active {
background-image: url(webcam_on.png);
}
#webcam-control {
background-image: url(webcam.png);
}
#webcam-control.muted {
background-image: url(webcam_mute.png);
}
#mic-control {
background-image: url(mic.png);
}
#mic-control.muted {
background-image: url(mic_muted.png);
}
#remoteVideos {
display:block;
position:relative;
/* top:-30px;*/
text-align:center;
height:196px;
width:auto;
overflow: hidden;
border:1px solid transparent;
font-size:0;
z-index: 2;
}
#remoteVideos video {
position:relative;
top:38px;
height:160px;
width:auto;
z-index:0;
border:1px solid transparent;
}
#remoteVideos video:hover {
cursor: pointer;
cursor: hand;
transform:scale(1.08, 1.08);
-webkit-transform:scale(1.08, 1.08);
transition-duration: 0.5s;
-webkit-transition-duration: 0.5s;
background-color: #FFFFFF;
-webkit-animation-name: greyPulse;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: 1;
-webkit-box-shadow: 0 0 18px #515151;
border:1px solid #FFFFFF;
z-index: 10;
}
#chatspace {
height:46px;
line-height:20px;
font-size:20px;
visibility:hidden;
padding:10px;
margin-left:auto;
margin-right:auto;
}
#chatspace .nick {
font-weight:bold;
}
#chatspace>:first-child{
visibility:hidden;
}
div#spacer {
height:5px;
}
#settings {
display:none;
}
#nowebrtc {
display:none;
}
div#header{
display:block;
position:relative;
width:100%;
height:39px;
z-index: 1;
}
div#left {
display:block;
position: absolute;
left: 0px;
top: 0px;
width: 100px;
height: 39px;
background-repeat:no-repeat;
margin: 0;
padding: 0;
}
div#leftlogo {
position:absolute;
top: 5px;
left: 15px;
background-image:url(jitsilogo.png);
background-repeat:no-repeat;
height: 31px;
width: 68px;
}
div#link {
margin-left: 100px;
margin-right:100px;
height:39px;
line-height:39px;
overflow: hidden;
text-align: center;
vertical-align:middle;
font-size: 14px;
color: #313131;
visibility:hidden;
}
.fade_line {
height: 2px;
background: black;
background: -webkit-gradient(linear, 0 0, 100% 0, from(white), to(white), color-stop(50%, black));
}
div#right {
display:block;
position:absolute;
right: 0px;
top: 0px;
background-repeat:no-repeat;
margin:0;
padding:0;
width:100px;
height:39px;
}
div#rightlogo {
position:absolute;
top: 8px;
right: 15px;
background-image:url(estoslogo.png);
background-repeat:no-repeat;
height: 20px;
width: 62px;
}
#largeVideo {width:1280px;height:720px;margin-left:auto;margin-right:255px;display:block; top:30px}
#localVideo {visibility:hidden; }
#remoteVideos {text-align:center;height:180px;}
#remoteVideos video {width:320;height:180;}
#chatspace {height:46px;line-height:20px;font-size:20px;visibility:hidden;padding:10px;margin-left:auto;margin-right:auto;}
#chatspace .nick {font-weight:bold;}
#chatspace>:first-child{visibility:hidden}
#settings {display:none;}
#header {text-align:center;visibility:hidden;}
\ No newline at end of file
/**
* Jitsi video bridge plugin for Candy
*
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Videobridge = (function(self, Candy, $) {
var connection = null;
var nickname = null;
var roomJid = null;
var previousRoomJid = null;
var room = null;
var previousRoom = null;
var videoOn = false;
self.init = function()
{
console.log("Videobridge.init");
window.RTC = setupRTC();
if (window.RTC == null) {
alert('Sorry, your browser is not WebRTC enabled!');
return;
} else if (window.RTC.browser != 'chrome') {
alert('Sorry, only Chrome supported for now!');
return;
}
getConstraints(['audio','video'], '360');
getUsermedia();
Candy.View.Event.Room.onAdd = handleOnAdd;
Candy.View.Event.Room.onShow = handleOnShow;
Candy.View.Event.Room.onHide = handleOnHide;
Candy.View.Event.Room.onClose = handleOnClose;
Candy.View.Event.Room.onPresenceChange = handleOnPresenceChange;
Candy.View.Event.Roster.onUpdate = handleRoster;
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT, chatObserver);
resizeLarge();
$(window).resize(function() {
resizeLarge();
});
}
var videoControl = function() {
console.log("videoControl ");
var html = "";
html += '<li id="videobridge-control" data-tooltip="add or remove video"></li>';
html += '<li id="webcam-control" data-tooltip="mute/toggle video mute"></li>';
html += '<li id="mic-control" data-tooltip="toggle audio mute"></li>';
$('#chat-toolbar').prepend(html);
$('#videobridge-control').click(function() {
$(this).toggleClass('active');
if (videoOn)
sendExpireCommand(roomJid);
else
sendOfferCommand(roomJid);
});
$('#webcam-control').click(function() {
$(this).toggleClass('muted');
if (window.RTC.rayo.localStream)
{
for (var idx = 0; idx < window.RTC.rayo.localStream.getVideoTracks().length; idx++) {
window.RTC.rayo.localStream.getVideoTracks()[idx].enabled = !window.RTC.rayo.localStream.getVideoTracks()[idx].enabled;
}
}
});
$('#mic-control').click(function() {
$(this).toggleClass('muted');
if (window.RTC.rayo.localStream)
{
for (var idx = 0; idx < window.RTC.rayo.localStream.getAudioTracks().length; idx++) {
window.RTC.rayo.localStream.getAudioTracks()[idx].enabled = !window.RTC.rayo.localStream.getAudioTracks()[idx].enabled;
}
}
});
$('#largeVideo').click(function() {
$("#largeVideo").css("visibility", "hidden");
});
};
var handleOnPresenceChange = function(arg) {
console.log("handleOnPresenceChange " + arg.roomJid);
};
var handleOnClose = function(arg) {
console.log("handleOnClose " + arg.roomJid);
sendExpireCommand(arg.roomJid);
};
var handleOnAdd = function(arg) {
console.log("handleOnAdd ", arg);
};
var handleOnHide = function(arg) {
console.log("handleOnHide", previousRoom, room);
};
var handleOnShow = function(arg)
{
console.log("handleOnShow ", arg);
previousRoom = room;
previousRoomJid = roomJid;
roomJid = arg.roomJid;
room = Strophe.getNodeFromJid(arg.roomJid);
if (videoOn && previousRoomJid && previousRoomJid != roomJid)
sendExpireCommand(previousRoomJid);
}
var handleRoster = function(args) {
console.log("handleRoster " + args.action + " " + args.user.getEscapedJid() + " " + args.roomJid);
};
var sendOfferCommand = function(roomJid)
{
console.log("sendOfferCommand ", roomJid);
if (window.RTC.rayo.localStream.getVideoTracks().length > 0)
{
$('.message-pane-wrapper').css("bottom", "110px");
$('#localVideo').css("visibility", "visible");
} else {
$('#remoteVideos').css("visibility", "hidden");
}
var offer = $iq({to: connection.domain, type: 'set'});
offer.c('colibri', {xmlns: 'urn:xmpp:rayo:colibri:1', action: 'offer', muc: roomJid});
connection.sendIQ(offer,
function (res) {
console.log('rayo colibri offer sent ok');
videoOn = true;
},
function (err) {
console.log('rayo colibri offer got error ', err);
$('#localVideo').css("visibility", "hidden");
$('.message-pane-wrapper').css("bottom", "0px");
}
);
}
var sendExpireCommand = function(roomJID)
{
console.log("sendExpireCommand ", roomJID);
$('#localVideo').css("visibility", "hidden");
connection.sendIQ($iq({to: connection.domain, type: 'set'}).c('colibri', {xmlns: 'urn:xmpp:rayo:colibri:1', action: 'expire', muc: roomJID}),
function (res) {
console.log('rayo colibri expire set ok');
videoOn = false;
$('.message-pane-wrapper').css("bottom", "0px");
$('#videobridge-control').removeClass('active');
},
function (err) {
console.log('rayo colibri expire got error', err);
$('.message-pane-wrapper').css("bottom", "0px");
$('#videobridge-control').removeClass('active');
}
);
setTimeout(function()
{
if (window.RTC.rayo.pc) window.RTC.rayo.pc.close();
}, 2000);
}
var chatObserver =
{
update: function(obj, args)
{
if(args.type === 'connection')
{
switch(args.status)
{
case Strophe.Status.CONNECTING:
connection = Candy.Core.getConnection();
break;
case Strophe.Status.CONNECTED:
nickname = Strophe.escapeNode(Candy.Core.getUser().getNick());
Candy.Core.addHandler(rayoCallback, 'urn:xmpp:rayo:colibri:1');
break;
}
}
}
};
var rayoCallback = function(presence) {
var from = $(presence).attr('from');
$(presence).find('offer').each(function()
{
handleOffer(from, this);
});
$(presence).find('removesource').each(function()
{
removeSSRC(from, this);
});
$(presence).find('addsource').each(function()
{
handleAddSSRC(from, this);
});
console.log("rayoCallback exit", presence);
return true;
};
var handleOffer = function (from, offer)
{
console.log("handleOffer", offer);
var bridgeSDP = new SDP('v=0\r\no=- 5151055458874951233 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nm=audio 1 RTP/SAVPF 111 0 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=sendrecv\r\na=rtpmap:111 opus/48000/2\r\na=fmtp:111 minptime=10\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:126 telephone-event/8000\r\na=maxptime:60\r\nm=video 1 RTP/SAVPF 100 116 117\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:video\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=sendrecv\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 goog-remb\r\na=rtpmap:116 red/90000\r\na=rtpmap:117 ulpfec/90000\r\n');
var muc = $(offer).attr('muc');
var nick = $(offer).attr('nickname');
var participant = $(offer).attr('participant');
var videobridge = $(offer).attr('videobridge');
var confid = null;
var channelId = [];
window.RTC.rayo.channels = {}
window.RTC.rayo.addssrc = false;
$(offer).find('conference').each(function()
{
confid = $(this).attr('id');
window.RTC.rayo.channels.id = confid;
$(this).find('content').each(function()
{
var name = $(this).attr('name');
var channel = name == "audio" ? 0 : 1;
if ((window.RTC.rayo.localStream.getVideoTracks().length > 0 && name == "video") || (window.RTC.rayo.localStream.getAudioTracks().length > 0 && name == "audio"))
{
console.log("handleOffer track", name);
$(this).find('channel').each(function()
{
channelId[channel] = $(this).attr('id');
$(this).find('source').each(function()
{
var ssrc = $(this).attr('ssrc');
if (ssrc)
{
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'cname:mixed' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'label:mixedlabela0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'msid:mixedmslabela0 mixedlabela0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'mslabel:mixedmslabela0' + '\r\n';
} else {
// make chrome happy... '3735928559' == 0xDEADBEEF
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'label:mixedlabelv0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'msid:mixedmslabelv0 mixedlabelv0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'mslabel:mixedmslabelv0' + '\r\n';
}
});
$(this).find('transport').each(function()
{
var pwd = $(this).attr('pwd');
var ufrag = $(this).attr('ufrag');
if (ufrag) bridgeSDP.media[channel] += 'a=ice-ufrag:' + ufrag + '\r\n';
if (pwd) bridgeSDP.media[channel] += 'a=ice-pwd:' + pwd + '\r\n';
$(this).find('candidate').each(function()
{
bridgeSDP.media[channel] += SDPUtil.candidateFromJingle(this);
});
$(this).find('fingerprint').each(function()
{
var hash = $(this).attr('hash');
var setup = $(this).attr('setup');
var fingerprint = $(this).text();
if (hash && fingerprint) bridgeSDP.media[channel] += 'a=fingerprint:' + hash + ' ' + fingerprint + '\r\n';
if (setup) bridgeSDP.media[channel] += 'a=setup:' + setup + '\r\n';
});
});
});
} else {
bridgeSDP.media[channel] = null;
}
});
});
bridgeSDP.raw = bridgeSDP.session + bridgeSDP.media.join('');
window.RTC.rayo.channels.sdp = bridgeSDP.raw;
//console.log("bridgeSDP.raw", bridgeSDP.raw);
window.RTC.rayo.pc = new window.RTC.peerconnection(null, {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]});
window.RTC.rayo.pc.onicecandidate = function(event)
{
//console.log('candidate', event.candidate);
if (!event.candidate)
{
sendAnswer(from, videobridge, confid, channelId);
}
}
window.RTC.rayo.pc.onaddstream = function(e)
{
console.log("onstream", e, window.RTC.rayo.addssrc);
if (window.RTC.rayo.pc.signalingState == "have-remote-offer")
$(document).trigger('remotestreamadded.rayo', [e, nick]);
window.RTC.rayo.pc.createAnswer(function(desc)
{
if (!window.RTC.rayo.addssrc)
window.RTC.rayo.pc.setLocalDescription(desc);
});
};
window.RTC.rayo.pc.addStream(window.RTC.rayo.localStream);
window.RTC.rayo.pc.setRemoteDescription(new RTCSessionDescription({type: "offer", sdp : bridgeSDP.raw}));
};
var sendAnswer = function(from, videobridge, confid, channelId)
{
console.log("sendAnswer");
var remoteSDP = new SDP(window.RTC.rayo.pc.localDescription.sdp);
//console.log("remoteSDP ", window.RTC.rayo.pc.localDescription.sdp);
var change = $iq({to: from, type: 'set'});
change.c('colibri', {xmlns: 'urn:xmpp:rayo:colibri:1', videobridge: videobridge});
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: confid});
for (channel = 0; channel < 2; channel++)
{
if (channelId[channel])
{
change.c('content', {name: channel === 0 ? 'audio' : 'video'});
change.c('channel', {id: channelId[channel]});
tmp = SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:');
change.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
tmp.forEach(function (line) {
var idx = line.indexOf(' ');
var linessrc = line.substr(0, idx).substr(7);
change.attrs({ssrc: linessrc});
var kv = line.substr(idx + 1);
change.c('parameter');
if (kv.indexOf(':') == -1) {
change.attrs({ name: kv });
} else {
change.attrs({ name: kv.split(':', 2)[0] });
change.attrs({ value: kv.split(':', 2)[1] });
}
change.up();
});
change.up(); // end of source
var rtpmap = SDPUtil.find_lines(remoteSDP.media[channel], 'a=rtpmap:');
rtpmap.forEach(function (val) {
var rtpmap = SDPUtil.parse_rtpmap(val);
change.c('payload-type', rtpmap);
change.up();
});
change.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
var fingerprints = SDPUtil.find_lines(remoteSDP.media[channel], 'a=fingerprint:', remoteSDP.session);
fingerprints.forEach(function (line)
{
var tmp = SDPUtil.parse_fingerprint(line);
tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
change.c('fingerprint').t(tmp.fingerprint);
delete tmp.fingerprint;
var line = SDPUtil.find_line(remoteSDP.media[channel], 'a=setup:', remoteSDP.session);
if (line) {
tmp.setup = line.substr(8);
}
change.attrs(tmp);
change.up();
});
var candidates = SDPUtil.find_lines(remoteSDP.media[channel], 'a=candidate:', remoteSDP.session);
candidates.forEach(function (line) {
var tmp = SDPUtil.candidateToJingle(line);
change.c('candidate', tmp).up();
});
tmp = SDPUtil.iceparams(remoteSDP.media[channel], remoteSDP.session);
if (tmp) {
change.attrs(tmp);
}
change.up(); // end of transport
change.up(); // end of channel
change.up(); // end of content
}
}
connection.sendIQ(change,
function (res) {
console.log('rayo colibri answer set ok', window.RTC.rayo.pc.signalingState);
},
function (err) {
console.log('rayo colibri answer got error ' + err);
}
);
};
var removeSSRC = function(from, removesource) {
var videobridge = $(removesource).attr('videobridge');
var active = $(removesource).attr('active');
var sdp = new SDP(window.RTC.rayo.pc.remoteDescription.sdp);
console.log("removeSSRC unmodified SDP", videobridge);
$(removesource).find('content').each(function()
{
var name = $(this).attr('name');
var ssrc = null;
$(this).find('source').each(function()
{
ssrc = $(this).attr('ssrc');
});
if (ssrc != null)
{
var idx = (name == "audio" ? 0 : 1);
sdp.removeMediaLines(idx, 'a=ssrc:' + ssrc);
}
});
sdp.raw = sdp.session + sdp.media.join('');
//console.log("removeSSRC modified SDP", sdp.raw);
window.RTC.rayo.pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}
), function() {
console.log('modify ok');
}, function(error) {
console.log('handleSSRC modify failed');
});
console.log("removeSSRC exit ", removesource);
};
var handleAddSSRC = function(from, addsource) {
var videobridge = $(addsource).attr('videobridge');
var sdp = new SDP(window.RTC.rayo.pc.remoteDescription.sdp);
console.log("handleAddSSRC unmodified SDP", videobridge);
$(addsource).find('content').each(function()
{
var name = $(this).attr('name');
var lines = '';
$(this).find('source').each(function()
{
var ssrc = $(this).attr('ssrc');
$(this).find('>parameter').each(function () {
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
lines += ':' + $(this).attr('value');
lines += '\r\n';
});
});
var idx = (name == "audio" ? 0 : 1);
sdp.media[idx] += lines;
});
sdp.raw = sdp.session + sdp.media.join('');
window.RTC.rayo.addssrc = true;
window.RTC.rayo.pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}
), function() {
console.log('modify ok', window.RTC.rayo.pc.signalingState);
}, function(error) {
console.log('handleSSRC modify failed');
});
console.log("handleSSRC exit ", addsource);
};
var handleAnswer = function(from, answer) {
console.log("handleAnswer", answer);
var bridgeSDP = new SDP('v=0\r\no=- 5151055458874951233 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=sendrecv\r\na=rtpmap:111 opus/48000/2\r\na=fmtp:111 minptime=10\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:126 telephone-event/8000\r\na=maxptime:60\r\nm=video 1 RTP/SAVPF 100 116 117\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:video\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=sendrecv\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 goog-remb\r\na=rtpmap:116 red/90000\r\na=rtpmap:117 ulpfec/90000\r\n');
var muc = $(answer).attr('muc');
var nick = $(answer).attr('nickname');
var participant = $(answer).attr('participant');
var videobridge = $(answer).attr('videobridge');
var confid = null;
var channelId = [];
$(answer).find('conference').each(function()
{
confid = $(this).attr('id');
$(this).find('content').each(function()
{
var name = $(this).attr('name');
var channel = name == "audio" ? 0 : 1;
$(this).find('channel').each(function()
{
channelId[channel] = $(this).attr('id');
$(this).find('source').each(function()
{
var ssrc = $(this).attr('ssrc');
;
if (ssrc)
{
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'cname:mixed' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'label:mixedlabela0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'msid:mixedmslabela0 mixedlabela0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' ' + 'mslabel:mixedmslabela0' + '\r\n';
} else {
// make chrome happy... '3735928559' == 0xDEADBEEF
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'label:mixedlabelv0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'msid:mixedmslabelv0 mixedlabelv0' + '\r\n';
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'mslabel:mixedmslabelv0' + '\r\n';
}
});
$(this).find('transport').each(function()
{
var pwd = $(this).attr('pwd');
var ufrag = $(this).attr('ufrag');
if (ufrag) bridgeSDP.media[channel] += 'a=ice-ufrag:' + ufrag + '\r\n';
if (pwd) bridgeSDP.media[channel] += 'a=ice-pwd:' + pwd + '\r\n';
$(this).find('candidate').each(function()
{
bridgeSDP.media[channel] += SDPUtil.candidateFromJingle(this);
});
$(this).find('fingerprint').each(function()
{
var hash = $(this).attr('hash');
var setup = $(this).attr('setup');
var fingerprint = $(this).text();
if (hash && fingerprint) bridgeSDP.media[channel] += 'a=fingerprint:' + hash + ' ' + fingerprint + '\r\n';
if (setup) bridgeSDP.media[channel] += 'a=setup:' + setup + '\r\n';
});
});
});
});
});
bridgeSDP.raw = bridgeSDP.session + bridgeSDP.media.join('');
window.RTC.rayo.pc.setRemoteDescription
(
new RTCSessionDescription({type: "answer", sdp: bridgeSDP.raw}),
function () {
console.log('setRemoteDescription success');
},
function (error) {
console.log('setRemoteDescription failed', error);
}
);
//console.log("bridgeSDP.raw", bridgeSDP.raw);
};
$(window).bind('beforeunload', function ()
{
if (connection && connection.connected) {
window.RTC.rayo.localStream.stop();
window.RTC.rayo.pc.close();
connection.disconnect();
}
});
$(document).bind('mediaready.rayo', function(event, stream) {
window.RTC.rayo.localStream = stream;
window.RTC.attachMediaStream($('#localVideo'), stream);
document.getElementById('localVideo').muted = true;
document.getElementById('localVideo').autoplay = true;
document.getElementById('localVideo').volume = 0;
document.getElementById('largeVideo').volume = 0;
document.getElementById('largeVideo').src = document.getElementById('localVideo').src;
console.log("mediaready.rayo");
videoControl();
});
$(document).bind('mediafailure.rayo', function(error) {
console.error('mediafailure.rayo ' + error);
});
$(document).bind('remotestreamadded.rayo', function(event, data, sid) {
console.log("remotestreamadded.rayo", sid);
var id = 'remoteVideo_' + sid + '_' + data.stream.id;
if (!document.getElementById(id))
{
var vid = document.createElement('video');
vid.id = id;
vid.autoplay = true;
vid.oncontextmenu = function() { return false; };
var remotes = document.getElementById('remoteVideos');
remotes.appendChild(vid);
}
var sel = $('#' + id);
sel.hide();
window.RTC.attachMediaStream(sel, data.stream);
if (sel.attr('id').indexOf('mixedmslabel') == -1) {
// ignore mixedmslabela0 and non room members
sel.show();
resizeThumbnails();
document.getElementById('largeVideo').volume = 1;
$('#largeVideo').attr('src', sel.attr('src'));
}
data.stream.onended = function() {
console.log('stream ended', this.id);
var src = $('#' + id).attr('src');
$('#' + id).remove();
if (src === $('#largeVideo').attr('src')) {
// this is currently displayed as large
// pick the last visible video in the row
// ... well, if nobody else is left, this picks the local video
var pick = $('#remoteVideos :visible:last').get(0);
// mute if localvideo
document.getElementById('largeVideo').volume = pick.volume;
document.getElementById('largeVideo').src = pick.src;
}
resizeThumbnails();
}
// FIXME: hover is bad, this causes flicker. How about moving this?
// remember that moving this in the DOM requires to play() again
sel.click(
function() {
console.log('hover in', $(this).attr('src'));
if ($("#largeVideo").css("visibility") == "hidden")
{
$("#largeVideo").css("visibility", "visible");
var newSrc = $(this).attr('src');
if ($('#largeVideo').attr('src') != newSrc) {
document.getElementById('largeVideo').volume = 1;
$('#largeVideo').fadeOut(300, function(){
$(this).attr('src', newSrc);
$(this).fadeIn(300);
});
}
} else {
$("#largeVideo").css("visibility", "hidden");
}
}
);
});
$(document).bind('callterminated.rayo', function(event, sid, reason) {
// FIXME
});
var resizeLarge = function() {
var availableHeight = window.innerHeight;
var numvids = $('#remoteVideos>video:visible').length;
if (numvids < 5)
availableHeight -= 100; // min thumbnail height for up to 4 videos
else
availableHeight -= 50; // min thumbnail height for more than 5 videos
availableHeight -= 79; // padding + link ontop
var availableWidth = window.innerWidth;
var aspectRatio = 16.0 / 9.0;
if (availableHeight < availableWidth / aspectRatio) {
availableWidth = Math.floor(availableHeight * aspectRatio);
}
if (availableWidth < 0 || availableHeight < 0) return;
$('#largeVideo').width(availableWidth);
$('#largeVideo').height(availableWidth/aspectRatio);
$('#chatspace').width(availableWidth);
resizeThumbnails() ;
}
var resizeThumbnails = function() {
// Calculate the available height, which is the inner window height minus 39px for the header
// minus 4px for the delimiter lines on the top and bottom of the large video,
// minus the 36px space inside the remoteVideos container used for highlighting shadow.
var availableHeight = window.innerHeight - $('#largeVideo').height() - 79;
var numvids = $('#remoteVideos>video:visible').length;
// Remove the 1px borders arround videos.
var availableWinWidth = $('#remoteVideos').width() - numvids*2;
var availableWidth = availableWinWidth / numvids;
var aspectRatio = 16.0 / 9.0;
var maxHeight = Math.min(160, availableHeight);
var availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
if (availableHeight < availableWidth / aspectRatio) {
availableWidth = Math.floor(availableHeight * aspectRatio);
}
// size videos so that while keeping AR and max height, we have a nice fit
$('#remoteVideos').height(availableHeight + 36); // add the 2*18px border used for highlighting shadow.
$('#remoteVideos>video:visible').width(availableWidth);
$('#remoteVideos>video:visible').height(availableHeight);
}
var setupRTC = function() {
var RTC = null;
if (navigator.mozGetUserMedia) {
console.log('This appears to be Firefox');
var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
if (version >= 22) {
RTC = {
rayo: {
pc: null,
channels: {},
addssrc: {},
localStream: null,
constraints: {audio: false, video: false}
},
peerconnection: mozRTCPeerConnection,
browser: 'firefox',
getUserMedia: navigator.mozGetUserMedia.bind(navigator),
attachMediaStream: function (element, stream) {
element[0].mozSrcObject = stream;
element[0].play();
},
pc_constraints: {}
};
if (!MediaStream.prototype.getVideoTracks)
MediaStream.prototype.getVideoTracks = function () { return []; };
if (!MediaStream.prototype.getAudioTracks)
MediaStream.prototype.getAudioTracks = function () { return []; };
RTCSessionDescription = mozRTCSessionDescription;
RTCIceCandidate = mozRTCIceCandidate;
}
} else if (navigator.webkitGetUserMedia) {
console.log('This appears to be Chrome');
RTC = {
rayo: {
pc: null,
channels: {},
addssrc: {},
localStream: null,
constraints: {audio: false, video: false}
},
peerconnection: webkitRTCPeerConnection,
browser: 'chrome',
getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
attachMediaStream: function (element, stream) {
element.attr('src', webkitURL.createObjectURL(stream));
},
// pc_constraints: {} // FIVE-182
pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]} // enable dtls support in canary
};
if (navigator.userAgent.indexOf('Android') != -1) {
RTC.pc_constraints = {}; // disable DTLS on Android
}
if (!webkitMediaStream.prototype.getVideoTracks) {
webkitMediaStream.prototype.getVideoTracks = function () {
return this.videoTracks;
};
}
if (!webkitMediaStream.prototype.getAudioTracks) {
webkitMediaStream.prototype.getAudioTracks = function () {
return this.audioTracks;
};
}
}
if (RTC === null) {
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
}
return RTC;
}
var getUsermedia = function()
{
console.log("getUsermedia", window.RTC.rayo.constraints);
try {
window.RTC.getUserMedia(window.RTC.rayo.constraints,
function (stream) {
console.log('onUserMediaSuccess');
$(document).trigger('mediaready.rayo', [stream]);
},
function (error) {
console.warn('Failed to get access to local media. Error ', error);
$(document).trigger('mediafailure.rayo');
});
} catch (e) {
console.error('GUM failed: ', e);
$(document).trigger('mediafailure.rayo');
}
};
var getConstraints = function(um, resolution, bandwidth, fps)
{
console.log("getConstraints", um, resolution, bandwidth, fps);
window.RTC.rayo.constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
window.RTC.rayo.constraints.video = {mandatory: {}};// same behaviour as true
}
if (um.indexOf('audio') >= 0) {
window.RTC.rayo.constraints.audio = {};// same behaviour as true
}
if (um.indexOf('screen') >= 0) {
window.RTC.rayo.constraints.video = {
"mandatory": {
"chromeMediaSource": "screen"
}
};
}
if (resolution && !window.RTC.rayo.constraints.video) {
window.RTC.rayo.constraints.video = {mandatory: {}};// same behaviour as true
}
// see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
switch (resolution) {
// 16:9 first
case '1080':
case 'fullhd':
window.RTC.rayo.constraints.video.mandatory.minWidth = 1920;
window.RTC.rayo.constraints.video.mandatory.minHeight = 1080;
window.RTC.rayo.constraints.video.mandatory.minAspectRatio = 1.77;
break;
case '720':
case 'hd':
window.RTC.rayo.constraints.video.mandatory.minWidth = 1280;
window.RTC.rayo.constraints.video.mandatory.minHeight = 720;
window.RTC.rayo.constraints.video.mandatory.minAspectRatio = 1.77;
break;
case '360':
window.RTC.rayo.constraints.video.mandatory.minWidth = 640;
window.RTC.rayo.constraints.video.mandatory.minHeight = 360;
window.RTC.rayo.constraints.video.mandatory.minAspectRatio = 1.77;
break;
case '180':
window.RTC.rayo.constraints.video.mandatory.minWidth = 320;
window.RTC.rayo.constraints.video.mandatory.minHeight = 180;
window.RTC.rayo.constraints.video.mandatory.minAspectRatio = 1.77;
break;
// 4:3
case '960':
window.RTC.rayo.constraints.video.mandatory.minWidth = 960;
window.RTC.rayo.constraints.video.mandatory.minHeight = 720;
break;
case '640':
case 'vga':
window.RTC.rayo.constraints.video.mandatory.minWidth = 640;
window.RTC.rayo.constraints.video.mandatory.minHeight = 480;
break;
case '320':
window.RTC.rayo.constraints.video.mandatory.minWidth = 320;
window.RTC.rayo.constraints.video.mandatory.minHeight = 240;
break;
default:
if (navigator.userAgent.indexOf('Android') != -1) {
window.RTC.rayo.constraints.video.mandatory.minWidth = 320;
window.RTC.rayo.constraints.video.mandatory.minHeight = 240;
window.RTC.rayo.constraints.video.mandatory.maxFrameRate = 15;
}
break;
}
if (bandwidth) { // doesn't work currently, see webrtc issue 1846
if (!window.RTC.rayo.constraints.video) window.RTC.rayo.constraints.video = {mandatory: {}};//same behaviour as true
window.RTC.rayo.constraints.video.optional = [{bandwidth: bandwidth}];
}
if (fps) { // for some cameras it might be necessary to request 30fps
// so they choose 30fps mjpg over 10fps yuy2
if (!window.RTC.rayo.constraints.video) window.RTC.rayo.constraints.video = {mandatory: {}};// same behaviour as tru;
window.RTC.rayo.constraints.video.mandatory.minFrameRate = fps;
}
}
return self;
}(CandyShop.Videobridge || {}, Candy, jQuery));
function SDP(sdp) {
this.media = sdp.split('\r\nm=');
for (var i = 1; i < this.media.length; i++) {
this.media[i] = 'm=' + this.media[i];
if (i != this.media.length - 1) {
this.media[i] += '\r\n';
}
}
this.session = this.media.shift() + '\r\n';
this.raw = this.session + this.media.join('');
}
// remove iSAC and CN from SDP
SDP.prototype.mangle = function () {
var i, j, mline, lines, rtpmap, newdesc;
for (i = 0; i < this.media.length; i++) {
lines = this.media[i].split('\r\n');
lines.pop(); // remove empty last element
mline = SDPUtil.parse_mline(lines.shift());
if (mline.media != 'audio')
continue;
newdesc = '';
mline.fmt.length = 0;
for (j = 0; j < lines.length; j++) {
if (lines[j].substr(0, 9) == 'a=rtpmap:') {
rtpmap = SDPUtil.parse_rtpmap(lines[j]);
if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
continue;
mline.fmt.push(rtpmap.id);
newdesc += lines[j] + '\r\n';
} else {
newdesc += lines[j] + '\r\n';
}
}
this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
this.media[i] += newdesc;
}
this.raw = this.session + this.media.join('');
};
// remove lines matching prefix from session section
SDP.prototype.removeSessionLines = function(prefix) {
var ob = this;
var lines = SDPUtil.find_lines(this.session, prefix);
lines.forEach(function(line) {
ob.session = ob.session.replace(line + '\r\n', '');
});
this.raw = this.session + this.media.join('');
return lines;
}
// remove lines matching prefix from a media section specified by mediaindex
// TODO: non-numeric mediaindex could match mid
SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
var ob = this;
var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
lines.forEach(function(line) {
ob.media[mediaindex] = ob.media[mediaindex].replace(line + '\r\n', '');
});
this.raw = this.session + this.media.join('');
return lines;
}
// add content's to a jingle element
SDP.prototype.toJingle = function (elem, thecreator) {
var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
var ob = this;
// new bundle plan
if (SDPUtil.find_line(this.session, 'a=group:')) {
lines = SDPUtil.find_lines(this.session, 'a=group:');
for (i = 0; i < lines.length; i++) {
tmp = lines[i].split(' ');
var semantics = tmp.shift().substr(8);
// new plan
elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', type: semantics, semantics:semantics});
for (j = 0; j < tmp.length; j++) {
elem.c('content', {name: tmp[j]}).up();
}
elem.up();
// temporary plan, to be removed
elem.c('group', {xmlns: 'urn:ietf:rfc:5888', type: semantics});
for (j = 0; j < tmp.length; j++) {
elem.c('content', {name: tmp[j]}).up();
}
elem.up();
}
}
// old bundle plan, to be removed
var bundle = [];
if (SDPUtil.find_line(this.session, 'a=group:BUNDLE')) {
bundle = SDPUtil.find_line(this.session, 'a=group:BUNDLE ').split(' ');
bundle.shift();
}
for (i = 0; i < this.media.length; i++) {
mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
if (!(mline.media == 'audio' || mline.media == 'video')) {
continue;
}
if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
} else {
ssrc = false;
}
elem.c('content', {creator: thecreator, name: mline.media});
if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
// prefer identifier from a=mid if present
var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
elem.attrs({ name: mid });
// old BUNDLE plan, to be removed
if (bundle.indexOf(mid) != -1) {
elem.c('bundle', {xmlns: 'http://estos.de/ns/bundle'}).up();
bundle.splice(bundle.indexOf(mid), 1);
}
}
if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) {
elem.c('description',
{xmlns: 'urn:xmpp:jingle:apps:rtp:1',
media: mline.media });
if (ssrc) {
elem.attrs({ssrc: ssrc});
}
for (j = 0; j < mline.fmt.length; j++) {
rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
// put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
for (k = 0; k < tmp.length; k++) {
elem.c('parameter', tmp[k]).up();
}
}
this.RtcpFbToJingle(this.media[i], elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
elem.up();
}
if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
elem.c('encryption', {required: 1});
var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
crypto.forEach(function(line) {
elem.c('crypto', SDPUtil.parse_crypto(line)).up();
});
elem.up(); // end of encryption
}
if (ssrc) {
// new style mapping
elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
// FIXME: group by ssrc and support multiple different ssrcs
var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
ssrclines.forEach(function(line) {
idx = line.indexOf(' ');
var linessrc = line.substr(0, idx).substr(7);
if (linessrc != ssrc) {
elem.up();
ssrc = linessrc;
elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
}
var kv = line.substr(idx + 1);
elem.c('parameter');
if (kv.indexOf(':') == -1) {
elem.attrs({ name: kv });
} else {
elem.attrs({ name: kv.split(':', 2)[0] });
elem.attrs({ value: kv.split(':', 2)[1] });
}
elem.up();
});
elem.up();
// old proprietary mapping, to be removed at some point
tmp = SDPUtil.parse_ssrc(this.media[i]);
tmp.xmlns = 'http://estos.de/ns/ssrc';
tmp.ssrc = ssrc;
elem.c('ssrc', tmp).up(); // ssrc is part of description
}
if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
elem.c('rtcp-mux').up();
}
// XEP-0293 -- map a=rtcp-fb:*
this.RtcpFbToJingle(this.media[i], elem, '*');
// XEP-0294
if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
for (j = 0; j < lines.length; j++) {
tmp = SDPUtil.parse_extmap(lines[j]);
elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
uri: tmp.uri,
id: tmp.value });
if (tmp.hasOwnProperty('direction')) {
switch (tmp.direction) {
case 'sendonly':
elem.attrs({senders: 'responder'});
break;
case 'recvonly':
elem.attrs({senders: 'initiator'});
break;
case 'sendrecv':
elem.attrs({senders: 'both'});
break;
case 'inactive':
elem.attrs({senders: 'none'});
break;
}
}
// TODO: handle params
elem.up();
}
}
elem.up(); // end of description
}
elem.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
// XEP-0320
var fingerprints = SDPUtil.find_lines(this.media[i], 'a=fingerprint:', this.session);
fingerprints.forEach(function(line) {
tmp = SDPUtil.parse_fingerprint(line);
tmp.xmlns = 'urn:xmpp:tmp:jingle:apps:dtls:0';
// tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0'; -- FIXME: update receivers first
elem.c('fingerprint').t(tmp.fingerprint);
delete tmp.fingerprint;
line = SDPUtil.find_line(ob.media[i], 'a=setup:', ob.session);
if (line) {
tmp.setup = line.substr(8);
}
elem.attrs(tmp);
elem.up();
});
tmp = SDPUtil.iceparams(this.media[i], this.session);
if (tmp) {
elem.attrs(tmp);
// XEP-0176
if (SDPUtil.find_line(this.media[i], 'a=candidate:', this.session)) { // add any a=candidate lines
lines = SDPUtil.find_lines(this.media[i], 'a=candidate:', this.session);
for (j = 0; j < lines.length; j++) {
tmp = SDPUtil.candidateToJingle(lines[j]);
elem.c('candidate', tmp).up();
}
}
elem.up(); // end of transport
}
if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
elem.attrs({senders: 'both'});
} else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
elem.attrs({senders: 'initiator'});
} else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
elem.attrs({senders: 'responder'});
} else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
elem.attrs({senders: 'none'});
}
if (mline.port == '0') {
// estos hack to reject an m-line
elem.attrs({senders: 'rejected'});
}
elem.up(); // end of content
}
elem.up();
return elem;
};
SDP.prototype.RtcpFbToJingle = function (sdp, elem, payloadtype) { // XEP-0293
var lines = SDPUtil.find_lines(sdp, 'a=rtcp-fb:' + payloadtype);
for (var i = 0; i < lines.length; i++) {
var tmp = SDPUtil.parse_rtcpfb(lines[i]);
if (tmp.type == 'trr-int') {
elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
elem.up();
} else {
elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
if (tmp.params.length > 0) {
elem.attrs({'subtype': tmp.params[0]});
}
elem.up();
}
}
};
SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
var media = '';
var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
if (tmp.length) {
media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
if (tmp.attr('value')) {
media += tmp.attr('value');
} else {
media += '0';
}
media += '\r\n';
}
tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
tmp.each(function () {
media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
if ($(this).attr('subtype')) {
media += ' ' + $(this).attr('subtype');
}
media += '\r\n';
});
return media;
};
// construct an SDP from a jingle stanza
SDP.prototype.fromJingle = function (jingle) {
var obj = this;
this.raw = 'v=0\r\n' +
'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
's=-\r\n' +
't=0 0\r\n';
// http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
try {
$(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
var contents = $(group).find('>content').map(function (idx, content) {
return $(content).attr('name');
}).get();
if (contents.length > 0) {
obj.raw += 'a=group:' + ($(group).attr('semantics') || $(group).attr('type')) + ' ' + contents.join(' ') + '\r\n';
}
});
} catch (e) { console.error(e.toString()); }
} else if ($(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').length) {
// temporary namespace, not to be used. to be removed soon.
$(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').each(function (idx, group) {
var contents = $(group).find('>content').map(function (idx, content) {
return $(content).attr('name');
}).get();
if ($(group).attr('type') !== null && contents.length > 0) {
obj.raw += 'a=group:' + $(group).attr('type') + ' ' + contents.join(' ') + '\r\n';
}
});
} else {
// for backward compability, to be removed soon
// assume all contents are in the same bundle group, can be improved upon later
var bundle = $(jingle).find('>content').filter(function (idx, content) {
//elem.c('bundle', {xmlns:'http://estos.de/ns/bundle'});
return $(content).find('>bundle').length > 0;
}).map(function (idx, content) {
return $(content).attr('name');
}).get();
if (bundle.length) {
this.raw += 'a=group:BUNDLE ' + bundle.join(' ') + '\r\n';
}
}
this.session = this.raw;
jingle.find('>content').each(function () {
var m = obj.jingle2media($(this));
obj.media.push(m);
});
// reconstruct msid-semantic -- apparently not necessary
/*
var msid = SDPUtil.parse_ssrc(this.raw);
if (msid.hasOwnProperty('mslabel')) {
this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
}
*/
this.raw = this.session + this.media.join('');
};
// translate a jingle content element into an an SDP media part
SDP.prototype.jingle2media = function (content) {
var media = '',
desc = content.find('description'),
ssrc = desc.attr('ssrc'),
self = this,
tmp;
tmp = { media: desc.attr('media') };
tmp.port = '1';
if (content.attr('senders') == 'rejected') {
// estos hack to reject an m-line.
tmp.port = '0';
}
if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
tmp.proto = 'RTP/SAVPF';
} else {
tmp.proto = 'RTP/AVPF';
}
tmp.fmt = desc.find('payload-type').map(function () { return $(this).attr('id'); }).get();
media += SDPUtil.build_mline(tmp) + '\r\n';
media += 'c=IN IP4 0.0.0.0\r\n';
media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
if (tmp.length) {
if (tmp.attr('ufrag')) {
media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
}
if (tmp.attr('pwd')) {
media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
}
tmp.find('>fingerprint').each(function () {
// FIXME: check namespace at some point
media += 'a=fingerprint:' + $(this).attr('hash');
media += ' ' + $(this).text();
media += '\r\n';
if ($(this).attr('setup')) {
media += 'a=setup:' + $(this).attr('setup') + '\r\n';
}
});
}
switch (content.attr('senders')) {
case 'initiator':
media += 'a=sendonly\r\n';
break;
case 'responder':
media += 'a=recvonly\r\n';
break;
case 'none':
media += 'a=inactive\r\n';
break;
case 'both':
media += 'a=sendrecv\r\n';
break;
}
media += 'a=mid:' + content.attr('name') + '\r\n';
// <description><rtcp-mux/></description>
// see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
// and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
if (desc.find('rtcp-mux').length) {
media += 'a=rtcp-mux\r\n';
}
if (desc.find('encryption').length) {
desc.find('encryption>crypto').each(function () {
media += 'a=crypto:' + $(this).attr('tag');
media += ' ' + $(this).attr('crypto-suite');
media += ' ' + $(this).attr('key-params');
if ($(this).attr('session-params')) {
media += ' ' + $(this).attr('session-params');
}
media += '\r\n';
});
}
desc.find('payload-type').each(function () {
media += SDPUtil.build_rtpmap(this) + '\r\n';
if ($(this).find('>parameter').length) {
media += 'a=fmtp:' + $(this).attr('id') + ' ';
media += $(this).find('parameter').map(function () { return ($(this).attr('name') ? ($(this).attr('name') + '=') : '') + $(this).attr('value'); }).get().join(';');
media += '\r\n';
}
// xep-0293
media += self.RtcpFbFromJingle($(this), $(this).attr('id'));
});
// xep-0293
media += self.RtcpFbFromJingle(desc, '*');
// xep-0294
tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
tmp.each(function () {
media += 'a=extmap:' + $(this).attr('id') + ' ' + $(this).attr('uri') + '\r\n';
});
content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
media += SDPUtil.candidateFromJingle(this);
});
tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
$(this).find('>parameter').each(function () {
media += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
media += ':' + $(this).attr('value');
media += '\r\n';
});
});
if (tmp.length === 0) {
// fallback to proprietary mapping of a=ssrc lines
tmp = content.find('description>ssrc[xmlns="http://estos.de/ns/ssrc"]');
if (tmp.length) {
media += 'a=ssrc:' + ssrc + ' cname:' + tmp.attr('cname') + '\r\n';
media += 'a=ssrc:' + ssrc + ' msid:' + tmp.attr('msid') + '\r\n';
media += 'a=ssrc:' + ssrc + ' mslabel:' + tmp.attr('mslabel') + '\r\n';
media += 'a=ssrc:' + ssrc + ' label:' + tmp.attr('label') + '\r\n';
}
}
return media;
};
SDPUtil = {
iceparams: function (mediadesc, sessiondesc) {
var data = null;
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
data = {
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
};
}
return data;
},
parse_iceufrag: function (line) {
return line.substring(12);
},
build_iceufrag: function (frag) {
return 'a=ice-ufrag:' + frag;
},
parse_icepwd: function (line) {
return line.substring(10);
},
build_icepwd: function (pwd) {
return 'a=ice-pwd:' + pwd;
},
parse_mid: function (line) {
return line.substring(6);
},
parse_mline: function (line) {
var parts = line.substring(2).split(' '),
data = {};
data.media = parts.shift();
data.port = parts.shift();
data.proto = parts.shift();
if (parts[parts.length - 1] === '') { // trailing whitespace
parts.pop();
}
data.fmt = parts;
return data;
},
build_mline: function (mline) {
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
},
parse_rtpmap: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.id = parts.shift();
parts = parts[0].split('/');
data.name = parts.shift();
data.clockrate = parts.shift();
data.channels = parts.length ? parts.shift() : '1';
return data;
},
build_rtpmap: function (el) {
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
line += '/' + el.getAttribute('channels');
}
return line;
},
parse_crypto: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.tag = parts.shift();
data['crypto-suite'] = parts.shift();
data['key-params'] = parts.shift();
if (parts.length) {
data['session-params'] = parts.join(' ');
}
return data;
},
parse_fingerprint: function (line) { // RFC 4572
var parts = line.substring(14).split(' '),
data = {};
data.hash = parts.shift();
data.fingerprint = parts.shift();
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
return data;
},
parse_fmtp: function (line) {
var parts = line.split(' '),
i, key, value,
data = [];
parts.shift();
parts = parts.join(' ').split(';');
for (i = 0; i < parts.length; i++) {
key = parts[i].split('=')[0];
while (key.length && key[0] == ' ') {
key = key.substring(1);
}
value = parts[i].split('=')[1];
if (key && value) {
data.push({name: key, value: value});
} else if (key) {
// rfc 4733 (DTMF) style stuff
data.push({name: '', value: key});
}
}
return data;
},
parse_icecandidate: function (line) {
var candidate = {},
elems = line.split(' ');
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
candidate.generation = 0; // default value, may be overwritten below
for (var i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
default: // TODO
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
build_icecandidate: function (cand) {
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
line += ' ';
switch (cand.type) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand['rel-addr'];
line += ' ';
line += 'rport';
line += ' ';
line += cand['rel-port'];
line += ' ';
}
break;
}
line += 'generation';
line += ' ';
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
return line;
},
parse_ssrc: function (desc) {
// proprietary mapping of a=ssrc lines
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
// and parse according to that
var lines = desc.split('\r\n'),
data = {};
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, 7) == 'a=ssrc:') {
var idx = lines[i].indexOf(' ');
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
}
}
return data;
},
parse_rtcpfb: function (line) {
var parts = line.substr(10).split(' ');
var data = {};
data.pt = parts.shift();
data.type = parts.shift();
data.params = parts;
return data;
},
parse_extmap: function (line) {
var parts = line.substr(9).split(' ');
var data = {};
data.value = parts.shift();
if (data.value.indexOf('/') != -1) {
data.direction = data.value.substr(data.value.indexOf('/') + 1);
data.value = data.value.substr(0, data.value.indexOf('/'));
} else {
data.direction = 'both';
}
data.uri = parts.shift();
data.params = parts;
return data;
},
find_line: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle) {
return lines[i];
}
}
if (!sessionpart) {
return false;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
return lines[j];
}
}
return false;
},
find_lines: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n'),
needles = [];
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle)
needles.push(lines[i]);
}
if (needles.length || !sessionpart) {
return needles;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
needles.push(lines[j]);
}
}
return needles;
},
candidateToJingle: function (line) {
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
if (line.substring(0, 12) != 'a=candidate:') {
console.log('parseCandidate called with a line that is not a candidate line');
console.log(line);
return null;
}
if (line.substring(line.length - 2) == '\r\n') // chomp it
line = line.substring(0, line.length - 2);
var candidate = {},
elems = line.split(' '),
i;
if (elems[6] != 'typ') {
console.log('did not find typ in the right place');
console.log(line);
return null;
}
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
for (i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
default: // TODO
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
candidateFromJingle: function (cand) {
var line = 'a=candidate:';
line += cand.getAttribute('foundation');
line += ' ';
line += cand.getAttribute('component');
line += ' ';
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
line += ' ';
line += cand.getAttribute('priority');
line += ' ';
line += cand.getAttribute('ip');
line += ' ';
line += cand.getAttribute('port');
line += ' ';
line += 'typ';
line += ' ' + cand.getAttribute('type');
line += ' ';
switch (cand.getAttribute('type')) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand.getAttribute('rel-addr');
line += ' ';
line += 'rport';
line += ' ';
line += cand.getAttribute('rel-port');
line += ' ';
}
break;
}
line += 'generation';
line += ' ';
line += cand.getAttribute('generation') || '0';
return line + '\r\n';
}
};
\ No newline at end of file
/**
* Chat CSS
*
* @author Michael <michael.weibel@gmail.com>
* @author Patrick <patrick.stadler@gmail.com>
* @copyright 2011 Amiado Group AG, All rights reserved.
*/
html, body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
}
#candy {
position: absolute;
top: 0;
bottom: 0px;
right: 0;
left: 0;
background-color: #ccc;
color: #333;
overflow: hidden;
}
a {
color: #333;
text-decoration: none;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
#chat-tabs {
list-style: none;
margin: 0 250px 0 0;
padding: 0;
overflow: auto;
overflow-y: hidden;
}
#chat-tabs li {
margin: 0;
float: left;
position: relative;
border-right: 1px solid #aaa;
white-space: nowrap;
}
#chat-tabs li a {
background-color: #ddd;
padding: 6px 50px 4px 10px;
display: inline-block;
color: #666;
height: 20px;
}
#chat-tabs li.active a {
background-color: white;
color: black;
}
#chat-tabs li a.transition {
position: absolute;
top: 0;
right: 0;
padding: 0;
width: 35px;
height: 30px;
background: url(img/tab-transitions.png) repeat-y left;
}
#chat-tabs li a.close {
background-color: transparent;
position: absolute;
top: 0;
right: 0;
height: auto;
padding: 5px;
margin: 1px 5px 0 2px;
color: #999;
}
#chat-tabs li.active a.transition {
color: gray;
background: url(img/tab-transitions.png) repeat-y -50px;
}
#chat-tabs li a.close:hover, #chat-tabs li.active a.close:hover {
color: black;
}
#chat-tabs li .unread {
color: white;
background-color: #9b1414;
padding: 2px 4px;
font-weight: bold;
font-size: 10px;
position: absolute;
top: 6px;
right: 22px;
border-radius: 3px;
}
#chat-tabs li.offline a.label {
text-decoration: line-through;
}
#chat-toolbar {
position: fixed;
bottom: 0;
right: 0;
font-size: 11px;
color: #666;
width: 250px;
height: 24px;
padding-top: 7px;
border-top: 1px solid #aaa;
background-color: #d9d9d9;
display: none;
}
#chat-toolbar li {
width: 16px;
height: 16px;
margin-left: 5px;
float: left;
display: inline-block;
cursor: pointer;
background-position: top left;
background-repeat: no-repeat;
}
#chat-toolbar #emoticons-icon {
background-image: url(img/action/emoticons.png);
}
#chat-toolbar .context {
background-image: url(img/action/settings.png);
display: none;
}
.role-moderator #chat-toolbar .context, .affiliation-owner #chat-toolbar .context {
display: inline-block;
}
#chat-sound-control {
background-image: url(img/action/sound-off.png);
}
#chat-sound-control.checked {
background-image: url(img/action/sound-on.png);
}
#chat-autoscroll-control {
background-image: url(img/action/autoscroll-off.png);
}
#chat-autoscroll-control.checked {
background-image: url(img/action/autoscroll-on.png);
}
#chat-statusmessage-control {
background: url(img/action/statusmessage-off.png);
}
#chat-statusmessage-control.checked {
background: url(img/action/statusmessage-on.png);
}
#chat-toolbar .usercount {
background-image: url(img/action/usercount.png);
cursor: default;
padding-left: 20px;
width: auto;
margin-right: 5px;
float: right;
}
.usercount span {
display: inline-block;
padding: 1px 3px;
background-color: #ccc;
font-weight: bold;
border-radius: 3px;
}
.room-pane {
display: none;
}
.roster-pane {
position: absolute;
overflow: auto;
top: 0;
right: 0;
bottom: 0;
width: 250px;
margin: 30px 0 32px 0;
}
.roster-pane .user {
cursor: pointer;
padding: 4px 7px;
font-size: 12px;
margin: 0 4px 2px 4px;
opacity: 0;
display: none;
color: #666;
clear: both;
height: 15px;
background-color: #ddd;
}
.roster-pane .user:hover {
background-color: #eee;
}
.roster-pane .user.status-ignored {
cursor: default;
}
.roster-pane .user.me {
font-weight: bold;
cursor: default;
}
.roster-pane .user.me:hover {
background-color: #ddd;
}
.roster-pane .label {
float: left;
width: 110px;
overflow: hidden;
white-space: nowrap;
}
.roster-pane li {
width: 16px;
height: 16px;
float: right;
display: block;
margin-left: 3px;
background-repeat: no-repeat;
background-position: center;
}
.roster-pane li.role {
cursor: default;
display: none;
}
.roster-pane li.role-moderator {
background-image: url(img/roster/role-moderator.png);
display: block;
}
.roster-pane li.affiliation-owner {
background-image: url(img/roster/affiliation-owner.png);
display: block;
}
.roster-pane li.ignore {
background-image: url(img/roster/ignore.png);
display: none;
}
.roster-pane .status-ignored li.ignore {
display: block;
}
.roster-pane .me li.context {
display: none;
}
.roster-pane li.context {
background-image: url(img/action/menu.png);
cursor: pointer;
}
.roster-pane li.context:hover {
background-color: #ccc;
border-radius: 4px;
}
.message-pane-wrapper {
clear: both;
overflow: auto;
position: absolute;
top: 0;
right: 0;
bottom: 0px;
left: 0;
height: auto;
width: auto;
margin: 30px 250px 32px 0;
background-color: white;
font-size: 13px;
}
.message-pane {
margin: 0;
padding: 5px 10px 2px 10px;
}
.message-pane dt {
width: 55px;
float: left;
color: #888;
font-size: 10px;
text-align: right;
padding-top: 4px;
}
.message-pane dd {
overflow: auto;
padding: 2px 0 1px 130px;
margin: 0 0 2px 0;
white-space: -o-pre-wrap; /* Opera */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
.message-pane dd .label {
font-weight: bold;
white-space: nowrap;
display: block;
margin-left: -125px;
width: 120px;
float: left;
overflow: hidden;
}
.message-pane .subject {
color: #a00;
font-weight: bold;
}
.message-pane .adminmessage {
color: #a00;
font-weight: bold;
}
.message-pane .infomessage {
color: #888;
font-style: italic;
padding-left: 5px;
}
.message-pane .emoticon {
vertical-align: text-bottom;
height: 15px;
width: 15px;
}
.message-form-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: auto;
margin-right: 250px;
border-top: 1px solid #ccc;
background-color: #f2f2f2;
height: 31px;
}
.message-form {
position: fixed;
bottom: 0;
left: 0;
right: 0;
margin-right: 320px;
padding: 0;
}
.message-form input {
border: 0 none;
padding: 5px 10px;
font-size: 14px;
width: 100%;
display: block;
outline-width: 0;
background-color: #f2f2f2;
}
.message-form input.submit {
cursor: pointer;
background-color: #ccc;
color: #666;
position: fixed;
bottom: 0;
right: 0;
margin: 3px 253px 3px 3px;
padding: 5px 7px;
width: auto;
font-size: 12px;
line-height: 12px;
height: 25px;
font-weight: bold;
border-radius: 10px;
}
#tooltip {
position: absolute;
z-index: 10;
display: none;
margin: 18px -18px 2px -2px;
color: white;
font-size: 11px;
padding: 5px 0;
background: url(img/tooltip-arrows.gif) no-repeat left bottom;
}
#tooltip div {
background-color: black;
padding: 2px 5px;
zoom: 1;
}
#context-menu {
position: absolute;
z-index: 10;
display: none;
padding: 15px 10px;
margin: 8px -28px -8px -12px;
background: url(img/context-arrows.gif) no-repeat left bottom;
}
#context-menu ul {
background-color: black;
color: white;
font-size: 12px;
padding: 2px;
zoom: 1;
}
#context-menu li {
padding: 3px 5px 3px 20px;
line-height: 12px;
cursor: pointer;
margin-bottom: 2px;
background: 1px no-repeat;
white-space: nowrap;
}
#context-menu li:hover {
background-color: #666;
}
#context-menu li:last-child {
margin-bottom: 0;
}
#context-menu .private {
background-image: url(img/action/private.png);
}
#context-menu .ignore {
background-image: url(img/action/ignore.png);
}
#context-menu .unignore {
background-image: url(img/action/unignore.png);
}
#context-menu .kick {
background-image: url(img/action/kick.png);
}
#context-menu .ban {
background-image: url(img/action/ban.png);
}
#context-menu .subject {
background-image: url(img/action/subject.png);
}
#context-menu .emoticons {
padding-left: 5px;
width: 85px;
white-space: normal;
}
#context-menu .emoticons:hover {
background-color: transparent;
}
#context-menu .emoticons img {
cursor: pointer;
margin: 3px;
height: 15px;
width: 15px;
}
#chat-modal {
background: url(img/modal-bg.png);
width: 300px;
padding: 20px 5px;
color: white;
font-size: 16px;
position: fixed;
left: 50%;
top: 50%;
margin-left: -155px;
margin-top: -45px;
text-align: center;
display: none;
z-index: 100;
border-radius: 5px;
}
#chat-modal-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 90;
background-image: url(img/overlay.png);
}
#chat-modal.modal-login {
display: block;
margin-top: -100px;
}
#chat-modal-spinner {
display: none;
margin-left: 15px;
}
#chat-modal form {
margin: 15px 0;
}
#chat-modal label, #chat-modal input, #chat-modal select {
display: block;
float: left;
line-height: 26px;
font-size: 16px;
margin: 5px 0;
}
#chat-modal input, #chat-modal select {
padding: 2px;
line-height: 16px;
width: 150px;
}
#chat-modal label {
text-align: right;
padding-right: 1em;
clear: both;
width: 100px;
}
#chat-modal input.button {
float: none;
display: block;
margin: 5px auto;
clear: both;
position: relative;
top: 10px;
width: 250px;
}
#chat-modal .close {
position: absolute;
right: 0;
display: none;
padding: 0 5px;
margin: -17px 3px 0 0;
color: white;
border-radius: 3px;
}
#chat-modal .close:hover {
background-color: #333;
}
Simple Smileys is a set of 49 clean, free as in freedom, Public Domain smileys.
For more packages or older versions, visit http://simplesmileys.org
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/*jslint regexp: true, browser: true, confusion: true, sloppy: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
/*global jQuery: true, MD5: true, escape: true, Mustache: true, console: true, Strophe: true, $iq: true, $pres: true */
/** Class: Candy
* Candy base class for initalizing the view and the core
*
* Parameters:
* (Candy) self - itself
* (jQuery) $ - jQuery
*/
var Candy = (function(self, $) {
/** Object: about
* About candy
*
* Contains:
* (String) name - Candy
* (Float) version - Candy version
*/
self.about = {
name: 'Candy',
version: '1.0.9'
};
/** Function: init
* Init view & core
*
* Parameters:
* (String) service - URL to the BOSH interface
* (Object) options - Options for candy
*
* Options:
* (Boolean) debug - Debug (Default: false)
* (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
*/
self.init = function(service, options) {
self.View.init($('#candy'), options.view);
self.Core.init(service, options.core);
};
return self;
}(Candy || {}, jQuery));
/** File: core.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core
* Candy Chat Core
*
* Parameters:
* (Candy.Core) self - itself
* (Strophe) Strophe - Strophe JS
* (jQuery) $ - jQuery
*/
Candy.Core = (function(self, Strophe, $) {
/** PrivateVariable: _connection
* Strophe connection
*/
var _connection = null,
/** PrivateVariable: _service
* URL of BOSH service
*/
_service = null,
/** PrivateVariable: _user
* Current user (me)
*/
_user = null,
/** PrivateVariable: _rooms
* Opened rooms, containing instances of Candy.Core.ChatRooms
*/
_rooms = {},
/** PrivateVariable: _anonymousConnection
* Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
*/
_anonymousConnection = false,
/** PrivateVariable: _options
* Options:
* (Boolean) debug - Debug (Default: false)
* (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
*/
_options = {
/** Boolean: autojoin
* If set to `true` try to get the bookmarks and autojoin the rooms (supported by Openfire).
* You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
*/
autojoin: true,
debug: false
},
/** PrivateFunction: _addNamespace
* Adds a namespace.
*
* Parameters:
* (String) name - namespace name (will become a constant living in Strophe.NS.*)
* (String) value - XML Namespace
*/
_addNamespace = function(name, value) {
Strophe.addNamespace(name, value);
},
/** PrivateFunction: _addNamespaces
* Adds namespaces needed by Candy.
*/
_addNamespaces = function() {
_addNamespace('PRIVATE', 'jabber:iq:private');
_addNamespace('BOOKMARKS', 'storage:bookmarks');
_addNamespace('PRIVACY', 'jabber:iq:privacy');
_addNamespace('DELAY', 'jabber:x:delay');
},
/** PrivateFunction: _registerEventHandlers
* Adds listening handlers to the connection.
*/
_registerEventHandlers = function() {
self.addHandler(self.Event.Jabber.Version, Strophe.NS.VERSION, 'iq');
self.addHandler(self.Event.Jabber.Presence, null, 'presence');
self.addHandler(self.Event.Jabber.Message, null, 'message');
self.addHandler(self.Event.Jabber.Bookmarks, Strophe.NS.PRIVATE, 'iq');
self.addHandler(self.Event.Jabber.Room.Disco, Strophe.NS.DISCO_INFO, 'iq');
self.addHandler(self.Event.Jabber.PrivacyList, Strophe.NS.PRIVACY, 'iq', 'result');
self.addHandler(self.Event.Jabber.PrivacyListError, Strophe.NS.PRIVACY, 'iq', 'error');
};
/** Function: init
* Initialize Core.
*
* Parameters:
* (String) service - URL of BOSH service
* (Object) options - Options for candy
*/
self.init = function(service, options) {
_service = service;
// Apply options
$.extend(true, _options, options);
// Enable debug logging
if(_options.debug) {
self.log = function(str) {
try { // prevent erroring
if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
console.log(str);
}
} catch(e) {
//console.error(e);
}
};
self.log('[Init] Debugging enabled');
}
_addNamespaces();
// Connect to BOSH service
_connection = new Strophe.Connection(_service);
_connection.rawInput = self.rawInput.bind(self);
_connection.rawOutput = self.rawOutput.bind(self);
// Window unload handler... works on all browsers but Opera. There is NO workaround.
// Opera clients getting disconnected 1-2 minutes delayed.
window.onbeforeunload = self.onWindowUnload;
// Prevent Firefox from aborting AJAX requests when pressing ESC
if($.browser.mozilla) {
$(document).keydown(function(e) {
if(e.which === 27) {
e.preventDefault();
}
});
}
};
/** Function: connect
* Connect to the jabber host.
*
* There are four different procedures to login:
* connect('JID', 'password') - Connect a registered user
* connect('domain') - Connect anonymously to the domain. The user should receive a random JID.
* connect('domain', null, 'nick') - Connect anonymously to the domain. The user should receive a random JID but with a nick set.
* connect('JID') - Show login form and prompt for password. JID input is hidden.
* connect() - Show login form and prompt for JID and password.
*
* See:
* <Candy.Core.attach()> for attaching an already established session.
*
* Parameters:
* (String) jidOrHost - JID or Host
* (String) password - Password of the user
* (String) nick - Nick of the user. Set one if you want to anonymously connect but preset a nick. If jidOrHost is a domain
* and this param is not set, Candy will prompt for a nick.
*/
self.connect = function(jidOrHost, password, nick) {
// Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
_connection.reset();
_registerEventHandlers();
_anonymousConnection = !_anonymousConnection ? jidOrHost && jidOrHost.indexOf("@") < 0 : true;
if(jidOrHost && password) {
// authentication
_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + Candy.about.name, password, Candy.Core.Event.Strophe.Connect);
_user = new self.ChatUser(jidOrHost, Strophe.getNodeFromJid(jidOrHost));
} else if(jidOrHost && nick) {
// anonymous connect
_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + Candy.about.name, null, Candy.Core.Event.Strophe.Connect);
_user = new self.ChatUser(null, nick); // set jid to null because we'll later receive it
} else if(jidOrHost) {
Candy.Core.Event.Login(jidOrHost);
} else {
// display login modal
Candy.Core.Event.Login();
}
};
_getEscapedJidFromJid = function(jid) {
var node = Strophe.getNodeFromJid(jid),
domain = Strophe.getDomainFromJid(jid);
return node ? Strophe.escapeNode(node) + '@' + domain : domain;
};
/** Function: attach
* Attach an already binded & connected session to the server
*
* _See_ Strophe.Connection.attach
*
* Parameters:
* (String) jid - Jabber ID
* (Integer) sid - Session ID
* (Integer) rid - rid
*/
self.attach = function(jid, sid, rid) {
_user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
_registerEventHandlers();
_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
};
/** Function: disconnect
* Leave all rooms and disconnect
*/
self.disconnect = function() {
if(_connection.connected) {
$.each(self.getRooms(), function() {
Candy.Core.Action.Jabber.Room.Leave(this.getJid());
});
_connection.disconnect();
}
};
/** Function: addHandler
* Wrapper for Strophe.Connection.addHandler() to add a stanza handler for the connection.
*
* Parameters:
* (Function) handler - The user callback.
* (String) ns - The namespace to match.
* (String) name - The stanza name to match.
* (String) type - The stanza type attribute to match.
* (String) id - The stanza id attribute to match.
* (String) from - The stanza from attribute to match.
* (String) options - The handler options
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
self.addHandler = function(handler, ns, name, type, id, from, options) {
return _connection.addHandler(handler, ns, name, type, id, from, options);
};
/** Function: getUser
* Gets current user
*
* Returns:
* Instance of Candy.Core.ChatUser
*/
self.getUser = function() {
return _user;
};
/** Function: setUser
* Set current user. Needed when anonymous login is used, as jid gets retrieved later.
*
* Parameters:
* (Candy.Core.ChatUser) user - User instance
*/
self.setUser = function(user) {
_user = user;
};
/** Function: getConnection
* Gets Strophe connection
*
* Returns:
* Instance of Strophe.Connection
*/
self.getConnection = function() {
return _connection;
};
/** Function: getRooms
* Gets all joined rooms
*
* Returns:
* Object containing instances of Candy.Core.ChatRoom
*/
self.getRooms = function() {
return _rooms;
};
/** Function: isAnonymousConnection
* Returns true if <Candy.Core.connect> was first called with a domain instead of a jid as the first param.
*
* Returns:
* (Boolean)
*/
self.isAnonymousConnection = function() {
return _anonymousConnection;
};
/** Function: getOptions
* Gets options
*
* Returns:
* Object
*/
self.getOptions = function() {
return _options;
};
/** Function: getRoom
* Gets a specific room
*
* Parameters:
* (String) roomJid - JID of the room
*
* Returns:
* If the room is joined, instance of Candy.Core.ChatRoom, otherwise null.
*/
self.getRoom = function(roomJid) {
if (_rooms[roomJid]) {
return _rooms[roomJid];
}
return null;
};
/** Function: onWindowUnload
* window.onbeforeunload event which disconnects the client from the Jabber server.
*/
self.onWindowUnload = function() {
// Enable synchronous requests because Safari doesn't send asynchronous requests within unbeforeunload events.
// Only works properly when following patch is applied to strophejs: https://github.com/metajack/strophejs/issues/16/#issuecomment-600266
_connection.sync = true;
self.disconnect();
_connection.flush();
};
/** Function: rawInput
* (Overridden from Strophe.Connection.rawInput)
*
* Logs all raw input if debug is set to true.
*/
self.rawInput = function(data) {
this.log('RECV: ' + data);
};
/** Function rawOutput
* (Overridden from Strophe.Connection.rawOutput)
*
* Logs all raw output if debug is set to true.
*/
self.rawOutput = function(data) {
this.log('SENT: ' + data);
};
/** Function: log
* Overridden to do something useful if debug is set to true.
*
* See: Candy.Core#init
*/
self.log = function() {};
return self;
}(Candy.Core || {}, Strophe, jQuery));
/** File: action.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.Action
* Chat Actions (basicly a abstraction of Jabber commands)
*
* Parameters:
* (Candy.Core.Action) self - itself
* (Strophe) Strophe - Strophe
* (jQuery) $ - jQuery
*/
Candy.Core.Action = (function(self, Strophe, $) {
/** Class: Candy.Core.Action.Jabber
* Jabber actions
*/
self.Jabber = {
/** Function: Version
* Replies to a version request
*
* Parameters:
* (jQuery.element) msg - jQuery element
*/
Version: function(msg) {
Candy.Core.getConnection().send($iq({type: 'result', to: msg.attr('from'), from: msg.attr('to'), id: msg.attr('id')}).c('query', {name: Candy.about.name, version: Candy.about.version, os: navigator.userAgent}));
},
/** Function: Roster
* Sends a request for a roster
*/
Roster: function() {
Candy.Core.getConnection().send($iq({type: 'get', xmlns: Strophe.NS.CLIENT}).c('query', {xmlns: Strophe.NS.ROSTER}).tree());
},
/** Function: Presence
* Sends a request for presence
*
* Parameters:
* (Object) attr - Optional attributes
*/
Presence: function(attr) {
Candy.Core.getConnection().send($pres(attr).tree());
},
/** Function: Services
* Sends a request for disco items
*/
Services: function() {
Candy.Core.getConnection().send($iq({type: 'get', xmlns: Strophe.NS.CLIENT}).c('query', {xmlns: Strophe.NS.DISCO_ITEMS}).tree());
},
/** Function: Autojoin
* When Candy.Core.getOptions().autojoin is true, request autojoin bookmarks (OpenFire)
*
* Otherwise, if Candy.Core.getOptions().autojoin is an array, join each channel specified.
*/
Autojoin: function() {
// Request bookmarks
if(Candy.Core.getOptions().autojoin === true) {
Candy.Core.getConnection().send($iq({type: 'get', xmlns: Strophe.NS.CLIENT}).c('query', {xmlns: Strophe.NS.PRIVATE}).c('storage', {xmlns: Strophe.NS.BOOKMARKS}).tree());
// Join defined rooms
} else if($.isArray(Candy.Core.getOptions().autojoin)) {
$.each(Candy.Core.getOptions().autojoin, function() {
self.Jabber.Room.Join(this.valueOf());
});
}
},
/** Function: ResetIgnoreList
* Create new ignore privacy list (and reset the old one, if it exists).
*/
ResetIgnoreList: function() {
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), id: 'set1'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('list', {name: 'ignore'}).c('item', {'action': 'allow', 'order': '0'}).tree());
},
/** Function: RemoveIgnoreList
* Remove an existing ignore list.
*/
RemoveIgnoreList: function() {
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), id: 'remove1'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('list', {name: 'ignore'}).tree());
},
/** Function: GetIgnoreList
* Get existing ignore privacy list when connecting.
*/
GetIgnoreList: function() {
Candy.Core.getConnection().send($iq({type: 'get', from: Candy.Core.getUser().getJid(), id: 'get1'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('list', {name: 'ignore'}).tree());
},
/** Function: SetIgnoreListActive
* Set ignore privacy list active
*/
SetIgnoreListActive: function() {
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), id: 'set2'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('active', {name:'ignore'}).tree());
},
/** Function: GetJidIfAnonymous
* On anonymous login, initially we don't know the jid and as a result, Candy.Core._user doesn't have a jid.
* Check if user doesn't have a jid and get it if necessary from the connection.
*/
GetJidIfAnonymous: function() {
if (!Candy.Core.getUser().getJid()) {
Candy.Core.log("[Jabber] Anonymous login");
Candy.Core.getUser().data.jid = Candy.Core.getConnection().jid;
}
},
/** Class: Candy.Core.Action.Jabber.Room
* Room-specific commands
*/
Room: {
/** Function: Join
* Requests disco of specified room and joins afterwards.
*
* TODO:
* maybe we should wait for disco and later join the room?
* but what if we send disco but don't want/can join the room
*
* Parameters:
* (String) roomJid - Room to join
* (String) password - [optional] Password for the room
*/
Join: function(roomJid, password) {
self.Jabber.Room.Disco(roomJid);
Candy.Core.getConnection().muc.join(roomJid, Candy.Core.getUser().getNick(), null, null, password);
},
/** Function: Leave
* Leaves a room.
*
* Parameters:
* (String) roomJid - Room to leave
*/
Leave: function(roomJid) {
Candy.Core.getConnection().muc.leave(roomJid, Candy.Core.getRoom(roomJid).getUser().getNick(), function() {});
},
/** Function: Disco
* Requests <disco info of a room at http://xmpp.org/extensions/xep-0045.html#disco-roominfo>.
*
* Parameters:
* (String) roomJid - Room to get info for
*/
Disco: function(roomJid) {
Candy.Core.getConnection().send($iq({type: 'get', from: Candy.Core.getUser().getJid(), to: roomJid, id: 'disco3'}).c('query', {xmlns: Strophe.NS.DISCO_INFO}).tree());
},
/** Function: Message
* Send message
*
* Parameters:
* (String) roomJid - Room to which send the message into
* (String) msg - Message
* (String) type - "groupchat" or "chat" ("chat" is for private messages)
*
* Returns:
* (Boolean) - true if message is not empty after trimming, false otherwise.
*/
Message: function(roomJid, msg, type) {
// Trim message
msg = $.trim(msg);
if(msg === '') {
return false;
}
Candy.Core.getConnection().muc.message(Candy.Util.escapeJid(roomJid), undefined, msg, type);
return true;
},
/** Function: IgnoreUnignore
* Checks if the user is already ignoring the target user, if yes: unignore him, if no: ignore him.
*
* Uses the ignore privacy list set on connecting.
*
* Parameters:
* (String) userJid - Target user jid
*/
IgnoreUnignore: function(userJid) {
Candy.Core.getUser().addToOrRemoveFromPrivacyList('ignore', userJid);
Candy.Core.Action.Jabber.Room.UpdatePrivacyList();
},
/** Function: UpdatePrivacyList
* Updates privacy list according to the privacylist in the currentUser
*/
UpdatePrivacyList: function() {
var currentUser = Candy.Core.getUser(),
iq = $iq({type: 'set', from: currentUser.getJid(), id: 'edit1'})
.c('query', {xmlns: 'jabber:iq:privacy' })
.c('list', {name: 'ignore'}),
privacyList = currentUser.getPrivacyList('ignore');
if (privacyList.length > 0) {
$.each(privacyList, function(index, jid) {
iq.c('item', {type:'jid', value: Candy.Util.escapeJid(jid), action: 'deny', order : index})
.c('message').up().up();
});
} else {
iq.c('item', {action: 'allow', order : '0'});
}
Candy.Core.getConnection().send(iq.tree());
},
/** Class: Candy.Core.Action.Jabber.Room.Admin
* Room administration commands
*/
Admin: {
/** Function: UserAction
* Kick or ban a user
*
* Parameters:
* (String) roomJid - Room in which the kick/ban should be done
* (String) userJid - Victim
* (String) type - "kick" or "ban"
* (String) msg - Reason
*
* Returns:
* (Boolean) - true if sent successfully, false if type is not one of "kick" or "ban".
*/
UserAction: function(roomJid, userJid, type, reason) {
var iqId,
itemObj = {nick: Strophe.escapeNode(Strophe.getResourceFromJid(userJid))};
switch(type) {
case 'kick':
iqId = 'kick1';
itemObj.role = 'none';
break;
case 'ban':
iqId = 'ban1';
itemObj.affiliation = 'outcast';
break;
default:
return false;
}
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), to: roomJid, id: iqId}).c('query', {xmlns: Strophe.NS.MUC_ADMIN }).c('item', itemObj).c('reason').t(reason).tree());
return true;
},
/** Function: SetSubject
* Sets subject (topic) of a room.
*
* Parameters:
* (String) roomJid - Room
* (String) subject - Subject to set
*/
SetSubject: function(roomJid, subject) {
Candy.Core.getConnection().muc.setTopic(roomJid, subject);
}
}
}
};
return self;
}(Candy.Core.Action || {}, Strophe, jQuery));
/** File: chatRoom.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.ChatRoom
* Candy Chat Room
*
* Parameters:
* (String) roomJid - Room jid
*/
Candy.Core.ChatRoom = function(roomJid) {
/** Object: room
* Object containing roomJid and name.
*/
this.room = {
jid: roomJid,
name: null
};
/** Variable: user
* Current local user of this room.
*/
this.user = null;
/** Variable: Roster
* Candy.Core.ChatRoster instance
*/
this.roster = new Candy.Core.ChatRoster();
/** Function: setUser
* Set user of this room.
*
* Parameters:
* (Candy.Core.ChatUser) user - Chat user
*/
this.setUser = function(user) {
this.user = user;
};
/** Function: getUser
* Get current local user
*
* Returns:
* (Object) - Candy.Core.ChatUser instance or null
*/
this.getUser = function() {
return this.user;
};
/** Function: getJid
* Get room jid
*
* Returns:
* (String) - Room jid
*/
this.getJid = function() {
return this.room.jid;
};
/** Function: setName
* Set room name
*
* Parameters:
* (String) name - Room name
*/
this.setName = function(name) {
this.room.name = name;
};
/** Function: getName
* Get room name
*
* Returns:
* (String) - Room name
*/
this.getName = function() {
return this.room.name;
};
/** Function: setRoster
* Set roster of room
*
* Parameters:
* (Candy.Core.ChatRoster) roster - Chat roster
*/
this.setRoster = function(roster) {
this.roster = roster;
};
/** Function: getRoster
* Get roster
*
* Returns
* (Candy.Core.ChatRoster) - instance
*/
this.getRoster = function() {
return this.roster;
};
};
/** File: chatRoster.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.ChatRoster
* Chat Roster
*/
Candy.Core.ChatRoster = function () {
/** Object: items
* Roster items
*/
this.items = {};
/** Function: add
* Add user to roster
*
* Parameters:
* (Candy.Core.ChatUser) user - User to add
*/
this.add = function(user) {
this.items[user.getJid()] = user;
};
/** Function: remove
* Remove user from roster
*
* Parameters:
* (String) jid - User jid
*/
this.remove = function(jid) {
delete this.items[jid];
};
/** Function: get
* Get user from roster
*
* Parameters:
* (String) jid - User jid
*
* Returns:
* (Candy.Core.ChatUser) - User
*/
this.get = function(jid) {
return this.items[jid];
};
/** Function: getAll
* Get all items
*
* Returns:
* (Object) - all roster items
*/
this.getAll = function() {
return this.items;
};
};
/** File: chatUser.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.ChatUser
* Chat User
*/
Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
/** Constant: ROLE_MODERATOR
* Moderator role
*/
this.ROLE_MODERATOR = 'moderator';
/** Constant: AFFILIATION_OWNER
* Affiliation owner
*/
this.AFFILIATION_OWNER = 'owner';
/** Object: data
* User data containing:
* - jid
* - nick
* - affiliation
* - role
* - privacyLists
* - customData to be used by e.g. plugins
*/
this.data = {
jid: jid,
nick: Strophe.unescapeNode(nick),
affiliation: affiliation,
role: role,
privacyLists: {},
customData: {}
};
/** Function: getJid
* Gets an unescaped user jid
*
* See:
* <Candy.Util.unescapeJid>
*
* Returns:
* (String) - jid
*/
this.getJid = function() {
if(this.data.jid) {
return Candy.Util.unescapeJid(this.data.jid);
}
return;
};
/** Function: getEscapedJid
* Escapes the user's jid (node & resource get escaped)
*
* See:
* <Candy.Util.escapeJid>
*
* Returns:
* (String) - escaped jid
*/
this.getEscapedJid = function() {
return Candy.Util.escapeJid(this.data.jid);
};
/** Function: getNick
* Gets user nick
*
* Returns:
* (String) - nick
*/
this.getNick = function() {
return Strophe.unescapeNode(this.data.nick);
};
/** Function: getRole
* Gets user role
*
* Returns:
* (String) - role
*/
this.getRole = function() {
return this.data.role;
};
/** Function: getAffiliation
* Gets user affiliation
*
* Returns:
* (String) - affiliation
*/
this.getAffiliation = function() {
return this.data.affiliation;
};
/** Function: isModerator
* Check if user is moderator. Depends on the room.
*
* Returns:
* (Boolean) - true if user has role moderator or affiliation owner
*/
this.isModerator = function() {
return this.getRole() === this.ROLE_MODERATOR || this.getAffiliation() === this.AFFILIATION_OWNER;
};
/** Function: addToOrRemoveFromPrivacyList
* Convenience function for adding/removing users from ignore list.
*
* Check if user is already in privacy list. If yes, remove it. If no, add it.
*
* Parameters:
* (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
* (String) jid - User jid to add/remove
*
* Returns:
* (Array) - Current privacy list.
*/
this.addToOrRemoveFromPrivacyList = function(list, jid) {
if (!this.data.privacyLists[list]) {
this.data.privacyLists[list] = [];
}
var index = -1;
if ((index = this.data.privacyLists[list].indexOf(jid)) !== -1) {
this.data.privacyLists[list].splice(index, 1);
} else {
this.data.privacyLists[list].push(jid);
}
return this.data.privacyLists[list];
};
/** Function: getPrivacyList
* Returns the privacy list of the listname of the param.
*
* Parameters:
* (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
*
* Returns:
* (Array) - Privacy List
*/
this.getPrivacyList = function(list) {
if (!this.data.privacyLists[list]) {
this.data.privacyLists[list] = [];
}
return this.data.privacyLists[list];
};
/** Function: isInPrivacyList
* Tests if this user ignores the user provided by jid.
*
* Parameters:
* (String) list - Privacy list
* (String) jid - Jid to test for
*
* Returns:
* (Boolean)
*/
this.isInPrivacyList = function(list, jid) {
if (!this.data.privacyLists[list]) {
return false;
}
return this.data.privacyLists[list].indexOf(jid) !== -1;
};
/** Function: setCustomData
* Stores custom data
*
* Parameter:
* (Object) data - Object containing custom data
*/
this.setCustomData = function(data) {
this.data.customData = data;
};
/** Function: getCustomData
* Retrieve custom data
*
* Returns:
* (Object) - Object containing custom data
*/
this.getCustomData = function() {
return this.data.customData;
};
};
/** File: event.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.Event
* Chat Events
*
* Parameters:
* (Candy.Core.Event) self - itself
* (Strophe) Strophe - Strophe
* (jQuery) $ - jQuery
* (Candy.Util.Observable) observable - Observable to mixin
*/
Candy.Core.Event = (function(self, Strophe, $, observable) {
/**
* Mixin observable
*/
var i;
for (i in observable) {
if (observable.hasOwnProperty(i)) {
self[i] = observable[i];
}
}
/** Enum: KEYS
* Observer keys
*
* CHAT - Chat events
* PRESENCE - Presence events
* MESSAGE - Message events
* LOGIN - Login event
*/
self.KEYS = {
CHAT: 1,
PRESENCE: 2,
MESSAGE: 3,
LOGIN: 4,
PRESENCE_ERROR: 5
};
/** Class: Candy.Core.Event.Strophe
* Strophe-related events
*/
self.Strophe = {
/** Function: Connect
* Acts on strophe status events and notifies view.
*
* Parameters:
* (Strophe.Status) status - Strophe statuses
*/
Connect: function(status) {
switch(status) {
case Strophe.Status.CONNECTED:
Candy.Core.log('[Connection] Connected');
Candy.Core.Action.Jabber.GetJidIfAnonymous();
// fall through because the same things need to be done :)
case Strophe.Status.ATTACHED:
Candy.Core.log('[Connection] Attached');
Candy.Core.Action.Jabber.Presence();
Candy.Core.Action.Jabber.Autojoin();
Candy.Core.Action.Jabber.GetIgnoreList();
break;
case Strophe.Status.DISCONNECTED:
Candy.Core.log('[Connection] Disconnected');
break;
case Strophe.Status.AUTHFAIL:
Candy.Core.log('[Connection] Authentication failed');
break;
case Strophe.Status.CONNECTING:
Candy.Core.log('[Connection] Connecting');
break;
case Strophe.Status.DISCONNECTING:
Candy.Core.log('[Connection] Disconnecting');
break;
case Strophe.Status.AUTHENTICATING:
Candy.Core.log('[Connection] Authenticating');
break;
case Strophe.Status.ERROR:
case Strophe.Status.CONNFAIL:
Candy.Core.log('[Connection] Failed (' + status + ')');
break;
default:
Candy.Core.log('[Connection] What?!');
break;
}
self.notifyObservers(self.KEYS.CHAT, { type: 'connection', status: status } );
}
};
/** Function: Login
* Notify view that the login window should be displayed
*
* Parameters:
* (String) presetJid - Preset user JID
*/
self.Login = function(presetJid) {
self.notifyObservers(self.KEYS.LOGIN, { presetJid: presetJid } );
};
/** Class: Candy.Core.Event.Jabber
* Jabber related events
*/
self.Jabber = {
/** Function: Version
* Responds to a version request
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Version: function(msg) {
Candy.Core.log('[Jabber] Version');
Candy.Core.Action.Jabber.Version($(msg));
return true;
},
/** Function: Presence
* Acts on a presence event
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Presence: function(msg) {
Candy.Core.log('[Jabber] Presence');
msg = $(msg);
if(msg.children('x[xmlns^="' + Strophe.NS.MUC + '"]').length > 0) {
if (msg.attr('type') === 'error') {
self.Jabber.Room.PresenceError(msg);
} else {
self.Jabber.Room.Presence(msg);
}
}
return true;
},
/** Function: Bookmarks
* Acts on a bookmarks event. When a bookmark has the attribute autojoin set, joins this room.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Bookmarks: function(msg) {
Candy.Core.log('[Jabber] Bookmarks');
// Autojoin bookmarks (Openfire)
$('conference', msg).each(function() {
var item = $(this);
if(item.attr('autojoin')) {
Candy.Core.Action.Jabber.Room.Join(item.attr('jid'));
}
});
return true;
},
/** Function: PrivacyList
* Acts on a privacy list event and sets up the current privacy list of this user.
*
* If no privacy list has been added yet, create the privacy list and listen again to this event.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - false to disable the handler after first call.
*/
PrivacyList: function(msg) {
Candy.Core.log('[Jabber] PrivacyList');
var currentUser = Candy.Core.getUser();
$('list[name="ignore"] item', msg).each(function() {
var item = $(this);
if (item.attr('action') === 'deny') {
currentUser.addToOrRemoveFromPrivacyList('ignore', item.attr('value'));
}
});
Candy.Core.Action.Jabber.SetIgnoreListActive();
return false;
},
/** Function: PrivacyListError
* Acts when a privacy list error has been received.
*
* Currently only handles the case, when a privacy list doesn't exist yet and creates one.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - false to disable the handler after first call.
*/
PrivacyListError: function(msg) {
Candy.Core.log('[Jabber] PrivacyListError');
// check if msg says that privacyList doesn't exist
if ($('error[code="404"][type="cancel"] item-not-found', msg)) {
Candy.Core.Action.Jabber.ResetIgnoreList();
Candy.Core.Action.Jabber.SetIgnoreListActive();
}
return false;
},
/** Function: Message
* Acts on room, admin and server messages and notifies the view if required.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Message: function(msg) {
Candy.Core.log('[Jabber] Message');
var msg = $(msg),
fromJid = msg.attr('from'),
type = msg.attr('type'),
toJid = msg.attr('to');
// Room message
if(fromJid !== Strophe.getDomainFromJid(fromJid) && (type === 'groupchat' || type === 'chat' || type === 'error')) {
self.Jabber.Room.Message(msg);
// Admin message
} else if(!toJid && fromJid === Strophe.getDomainFromJid(fromJid)) {
self.notifyObservers(self.KEYS.CHAT, { type: (type || 'message'), message: msg.children('body').text() });
// Server Message
} else if(toJid && fromJid === Strophe.getDomainFromJid(fromJid)) {
self.notifyObservers(self.KEYS.CHAT, { type: (type || 'message'), subject: msg.children('subject').text(), message: msg.children('body').text() });
}
return true;
},
/** Class: Candy.Core.Event.Jabber.Room
* Room specific events
*/
Room: {
/** Function: Leave
* Leaves a room and cleans up related data and notifies view.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Leave: function(msg) {
Candy.Core.log('[Jabber:Room] Leave');
var msg = $(msg),
from = msg.attr('from'),
roomJid = Strophe.getBareJidFromJid(from);
// if room is not joined yet, ignore.
if (!Candy.Core.getRoom(roomJid)) {
return false;
}
var roomName = Candy.Core.getRoom(roomJid).getName(),
item = msg.find('item'),
type = 'leave',
reason,
actor;
delete Candy.Core.getRooms()[roomJid];
// if user gets kicked, role is none and there's a status code 307
if(item.attr('role') === 'none') {
if(msg.find('status').attr('code') === '307') {
type = 'kick';
} else if(msg.find('status').attr('code') === '301') {
type = 'ban';
}
reason = item.find('reason').text();
actor = item.find('actor').attr('jid');
}
var user = new Candy.Core.ChatUser(from, Strophe.getResourceFromJid(from), item.attr('affiliation'), item.attr('role'));
self.notifyObservers(self.KEYS.PRESENCE, { 'roomJid': roomJid, 'roomName': roomName, 'type': type, 'reason': reason, 'actor': actor, 'user': user } );
return true;
},
/** Function: Disco
* Sets informations to rooms according to the disco info received.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Disco: function(msg) {
Candy.Core.log('[Jabber:Room] Disco');
var msg = $(msg),
roomJid = Strophe.getBareJidFromJid(msg.attr('from'));
// Client joined a room
if(!Candy.Core.getRooms()[roomJid]) {
Candy.Core.getRooms()[roomJid] = new Candy.Core.ChatRoom(roomJid);
}
// Room existed but room name was unknown
var roomName = msg.find('identity').attr('name'),
room = Candy.Core.getRoom(roomJid);
if(room.getName() === null) {
room.setName(roomName);
// Room name changed
}/*else if(room.getName() !== roomName && room.getUser() !== null) {
// NOTE: We want to notify the View here but jabber doesn't send anything when the room name changes :-(
}*/
return true;
},
/** Function: Presence
* Acts on various presence messages (room leaving, room joining, error presence) and notifies view.
*
* Parameters:
* (Object) msg - jQuery object of XML message
*
* Returns:
* (Boolean) - true
*/
Presence: function(msg) {
Candy.Core.log('[Jabber:Room] Presence');
var from = Candy.Util.unescapeJid(msg.attr('from')),
roomJid = Strophe.getBareJidFromJid(from),
presenceType = msg.attr('type');
// Client left a room
if(Strophe.getResourceFromJid(from) === Candy.Core.getUser().getNick() && presenceType === 'unavailable') {
self.Jabber.Room.Leave(msg);
return true;
}
// Client joined a room
var room = Candy.Core.getRoom(roomJid);
if(!room) {
Candy.Core.getRooms()[roomJid] = new Candy.Core.ChatRoom(roomJid);
room = Candy.Core.getRoom(roomJid);
}
var roster = room.getRoster(),
action, user,
item = msg.find('item');
// User joined a room
if(presenceType !== 'unavailable') {
var nick = Strophe.getResourceFromJid(from);
user = new Candy.Core.ChatUser(from, nick, item.attr('affiliation'), item.attr('role'));
// Room existed but client (myself) is not yet registered
if(room.getUser() === null && Candy.Core.getUser().getNick() === nick) {
room.setUser(user);
}
roster.add(user);
action = 'join';
// User left a room
} else {
action = 'leave';
if(item.attr('role') === 'none') {
if(msg.find('status').attr('code') === '307') {
action = 'kick';
} else if(msg.find('status').attr('code') === '301') {
action = 'ban';
}
}
user = roster.get(from);
roster.remove(from);
}
self.notifyObservers(self.KEYS.PRESENCE, {'roomJid': roomJid, 'roomName': room.getName(), 'user': user, 'action': action, 'currentUser': Candy.Core.getUser() } );
return true;
},
/** Function: PresenceError
* Acts when a presence of type error has been retrieved.
*
* Parameters:
* (Object) msg - jQuery object of XML message
*
* Returns:
* (Boolean) - true
*/
PresenceError: function(msg) {
Candy.Core.log('[Jabber:Room] Presence Error');
var from = Candy.Util.unescapeJid(msg.attr('from')),
roomJid = Strophe.getBareJidFromJid(from),
room = Candy.Core.getRooms()[roomJid],
roomName = room.getName();
// Presence error: Remove room from array to prevent error when disconnecting
delete room;
self.notifyObservers(self.KEYS.PRESENCE_ERROR, {'msg' : msg, 'type': msg.children('error').children()[0].tagName.toLowerCase(), 'roomJid': roomJid, 'roomName': roomName});
},
/** Function: Message
* Acts on various message events (subject changed, private chat message, multi-user chat message)
* and notifies view.
*
* Parameters:
* (String) msg - jQuery object of XML message
*
* Returns:
* (Boolean) - true
*/
Message: function(msg) {
Candy.Core.log('[Jabber:Room] Message');
// Room subject
var roomJid, message;
if(msg.children('subject').length > 0) {
roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr('from')));
message = { name: Strophe.getNodeFromJid(roomJid), body: msg.children('subject').text(), type: 'subject' };
// Error messsage
} else if(msg.attr('type') === 'error') {
var error = msg.children('error');
if(error.attr('code') === '500' && error.children('text').length > 0) {
roomJid = msg.attr('from');
message = { type: 'info', body: error.children('text').text() };
}
// Chat message
} else if(msg.children('body').length > 0) {
// Private chat message
if(msg.attr('type') === 'chat') {
roomJid = Candy.Util.unescapeJid(msg.attr('from'));
var bareRoomJid = Strophe.getBareJidFromJid(roomJid),
// if a 3rd-party client sends a direct message to this user (not via the room) then the username is the node and not the resource.
isNoConferenceRoomJid = !Candy.Core.getRoom(bareRoomJid),
name = isNoConferenceRoomJid ? Strophe.getNodeFromJid(roomJid) : Strophe.getResourceFromJid(roomJid);
message = { name: name, body: msg.children('body').text(), type: msg.attr('type'), isNoConferenceRoomJid: isNoConferenceRoomJid };
// Multi-user chat message
} else {
roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr('from')));
var resource = Strophe.getResourceFromJid(msg.attr('from'));
// Message from a user
if(resource) {
resource = Strophe.unescapeNode(resource);
message = { name: resource, body: msg.children('body').text(), type: msg.attr('type') };
// Message from server (XEP-0045#registrar-statuscodes)
} else {
message = { name: '', body: msg.children('body').text(), type: 'info' };
}
}
// Unhandled message
} else {
return true;
}
// besides the delayed delivery (XEP-0203), there exists also XEP-0091 which is the legacy delayed delivery.
// the x[xmlns=jabber:x:delay] is the format in XEP-0091.
var delay = msg.children('delay') ? msg.children('delay') : msg.children('x[xmlns="' + Strophe.NS.DELAY +'"]'),
timestamp = delay !== undefined ? delay.attr('stamp') : null;
self.notifyObservers(self.KEYS.MESSAGE, {roomJid: roomJid, message: message, timestamp: timestamp } );
return true;
}
}
};
return self;
}(Candy.Core.Event || {}, Strophe, jQuery, Candy.Util.Observable));
/** File: util.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Util
* Candy utils
*
* Parameters:
* (Candy.Util) self - itself
* (jQuery) $ - jQuery
*/
Candy.Util = (function(self, $){
/** Function: jidToId
* Translates a jid to a MD5-Id
*
* Parameters:
* (String) jid - Jid
*
* Returns:
* MD5-ified jid
*/
self.jidToId = function(jid) {
return MD5.hexdigest(jid);
};
/** Function: escapeJid
* Escapes a jid (node & resource get escaped)
*
* See:
* XEP-0106
*
* Parameters:
* (String) jid - Jid
*
* Returns:
* (String) - escaped jid
*/
self.escapeJid = function(jid) {
var node = Strophe.escapeNode(Strophe.getNodeFromJid(jid)),
domain = Strophe.getDomainFromJid(jid),
resource = Strophe.getResourceFromJid(jid);
jid = node + '@' + domain;
if (resource) {
jid += '/' + Strophe.escapeNode(resource);
}
return jid;
};
/** Function: unescapeJid
* Unescapes a jid (node & resource get unescaped)
*
* See:
* XEP-0106
*
* Parameters:
* (String) jid - Jid
*
* Returns:
* (String) - unescaped Jid
*/
self.unescapeJid = function(jid) {
var node = Strophe.unescapeNode(Strophe.getNodeFromJid(jid)),
domain = Strophe.getDomainFromJid(jid),
resource = Strophe.getResourceFromJid(jid);
jid = node + '@' + domain;
if(resource) {
jid += '/' + Strophe.unescapeNode(resource);
}
return jid;
};
/** Function: crop
* Crop a string with the specified length
*
* Parameters:
* (String) str - String to crop
* (Integer) len - Max length
*/
self.crop = function(str, len) {
if (str.length > len) {
str = str.substr(0, len - 3) + '...';
}
return str;
};
/** Function: setCookie
* Sets a new cookie
*
* Parameters:
* (String) name - cookie name
* (String) value - Value
* (Integer) lifetime_days - Lifetime in days
*/
self.setCookie = function(name, value, lifetime_days) {
var exp = new Date();
exp.setDate(new Date().getDate() + lifetime_days);
document.cookie = name + '=' + value + ';expires=' + exp.toUTCString() + ';path=/';
};
/** Function: cookieExists
* Tests if a cookie with the given name exists
*
* Parameters:
* (String) name - Cookie name
*
* Returns:
* (Boolean) - true/false
*/
self.cookieExists = function(name) {
return document.cookie.indexOf(name) > -1;
};
/** Function: getCookie
* Returns the cookie value if there's one with this name, otherwise returns undefined
*
* Parameters:
* (String) name - Cookie name
*
* Returns:
* Cookie value or undefined
*/
self.getCookie = function(name) {
if(document.cookie) {
var regex = new RegExp(escape(name) + '=([^;]*)', 'gm'),
matches = regex.exec(document.cookie);
if(matches) {
return matches[1];
}
}
};
/** Function: deleteCookie
* Deletes a cookie with the given name
*
* Parameters:
* (String) name - cookie name
*/
self.deleteCookie = function(name) {
document.cookie = name + '=;expires=Thu, 01-Jan-70 00:00:01 GMT;path=/';
};
/** Function: getPosLeftAccordingToWindowBounds
* Fetches the window width and element width
* and checks if specified position + element width is bigger
* than the window width.
*
* If this evaluates to true, the position gets substracted by the element width.
*
* Parameters:
* (jQuery.Element) elem - Element to position
* (Integer) pos - Position left
*
* Returns:
* Object containing `px` (calculated position in pixel) and `alignment` (alignment of the element in relation to pos, either 'left' or 'right')
*/
self.getPosLeftAccordingToWindowBounds = function(elem, pos) {
var windowWidth = $(document).width(),
elemWidth = elem.outerWidth(),
marginDiff = elemWidth - elem.outerWidth(true),
backgroundPositionAlignment = 'left';
if (pos + elemWidth >= windowWidth) {
pos -= elemWidth - marginDiff;
backgroundPositionAlignment = 'right';
}
return { px: pos, backgroundPositionAlignment: backgroundPositionAlignment };
};
/** Function: getPosTopAccordingToWindowBounds
* Fetches the window height and element height
* and checks if specified position + element height is bigger
* than the window height.
*
* If this evaluates to true, the position gets substracted by the element height.
*
* Parameters:
* (jQuery.Element) elem - Element to position
* (Integer) pos - Position top
*
* Returns:
* Object containing `px` (calculated position in pixel) and `alignment` (alignment of the element in relation to pos, either 'top' or 'bottom')
*/
self.getPosTopAccordingToWindowBounds = function(elem, pos) {
var windowHeight = $(document).height(),
elemHeight = elem.outerHeight(),
marginDiff = elemHeight - elem.outerHeight(true),
backgroundPositionAlignment = 'top';
if (pos + elemHeight >= windowHeight) {
pos -= elemHeight - marginDiff;
backgroundPositionAlignment = 'bottom';
}
return { px: pos, backgroundPositionAlignment: backgroundPositionAlignment };
};
/** Function: localizedTime
* Localizes ISO-8610 Date with the time/dateformat specified in the translation.
*
* See: libs/dateformat/dateFormat.js
* See: src/view/translation.js
* See: jquery-i18n/jquery.i18n.js
*
* Parameters:
* (String) dateTime - ISO-8610 Datetime
*
* Returns:
* If current date is equal to the date supplied, format with timeFormat, otherwise with dateFormat
*/
self.localizedTime = function(dateTime) {
if (dateTime === undefined) {
return undefined;
}
var date = self.iso8601toDate(dateTime);
if(date.toDateString() === new Date().toDateString()) {
return date.format($.i18n._('timeFormat'));
} else {
return date.format($.i18n._('dateFormat'));
}
};
/** Function: iso8610toDate
* Parses a ISO-8610 Date to a Date-Object.
*
* Uses a fallback if the client's browser doesn't support it.
*
* Quote:
* ECMAScript revision 5 adds native support for ISO-8601 dates in the Date.parse method,
* but many browsers currently on the market (Safari 4, Chrome 4, IE 6-8) do not support it.
*
* Credits:
* <Colin Snover at http://zetafleet.com/blog/javascript-dateparse-for-iso-8601>
*
* Parameters:
* (String) date - ISO-8610 Date
*
* Returns:
* Date-Object
*/
self.iso8601toDate = function(date) {
var timestamp = Date.parse(date), minutesOffset = 0;
if(isNaN(timestamp)) {
var struct = /^(\d{4}|[+\-]\d{6})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?))?/.exec(date);
if(struct) {
if(struct[8] !== 'Z') {
minutesOffset = +struct[10] * 60 + (+struct[11]);
if(struct[9] === '+') {
minutesOffset = -minutesOffset;
}
}
return new Date(+struct[1], +struct[2] - 1, +struct[3], +struct[4], +struct[5] + minutesOffset, +struct[6], struct[7] ? +struct[7].substr(0, 3) : 0);
} else {
// XEP-0091 date
timestamp = Date.parse(date.replace(/^(\d{4})(\d{2})(\d{2})/, '$1-$2-$3') + 'Z');
}
}
return new Date(timestamp);
};
/** Function: isEmptyObject
* IE7 doesn't work with jQuery.isEmptyObject (<=1.5.1), workaround.
*
* Parameters:
* (Object) obj - the object to test for
*
* Returns:
* Boolean true or false.
*/
self.isEmptyObject = function(obj) {
var prop;
for(prop in obj) {
if (obj.hasOwnProperty(prop)) {
return false;
}
}
return true;
};
/** Function: forceRedraw
* Fix IE7 not redrawing under some circumstances.
*
* Parameters:
* (jQuery.element) elem - jQuery element to redraw
*/
self.forceRedraw = function(elem) {
elem.css({display:'none'});
setTimeout(function() {
this.css({display:'block'});
}.bind(elem), 1);
};
/** Class: Candy.Util.Parser
* Parser for emoticons, links and also supports escaping.
*/
self.Parser = {
/** PrivateVariable: _emoticonPath
* Path to emoticons.
*
* Use setEmoticonPath() to change it
*/
_emoticonPath: '',
/** Function: setEmoticonPath
* Set emoticons location.
*
* Parameters:
* (String) path - location of emoticons with trailing slash
*/
setEmoticonPath: function(path) {
this._emoticonPath = path;
},
/** Array: emoticons
* Array containing emoticons to be replaced by their images.
*
* Can be overridden/extended.
*/
emoticons: [
{
plain: ':)',
regex: /((\s):-?\)|:-?\)(\s|$))/gm,
image: 'Smiling.png'
},
{
plain: ';)',
regex: /((\s);-?\)|;-?\)(\s|$))/gm,
image: 'Winking.png'
},
{
plain: ':D',
regex: /((\s):-?D|:-?D(\s|$))/gm,
image: 'Grinning.png'
},
{
plain: ';D',
regex: /((\s);-?D|;-?D(\s|$))/gm,
image: 'Grinning_Winking.png'
},
{
plain: ':(',
regex: /((\s):-?\(|:-?\((\s|$))/gm,
image: 'Unhappy.png'
},
{
plain: '^^',
regex: /((\s)\^\^|\^\^(\s|$))/gm,
image: 'Happy_3.png'
},
{
plain: ':P',
regex: /((\s):-?P|:-?P(\s|$))/igm,
image: 'Tongue_Out.png'
},
{
plain: ';P',
regex: /((\s);-?P|;-?P(\s|$))/igm,
image: 'Tongue_Out_Winking.png'
},
{
plain: ':S',
regex: /((\s):-?S|:-?S(\s|$))/igm,
image: 'Confused.png'
},
{
plain: ':/',
regex: /((\s):-?\/|:-?\/(\s|$))/gm,
image: 'Uncertain.png'
},
{
plain: '8)',
regex: /((\s)8-?\)|8-?\)(\s|$))/gm,
image: 'Sunglasses.png'
},
{
plain: '$)',
regex: /((\s)\$-?\)|\$-?\)(\s|$))/gm,
image: 'Greedy.png'
},
{
plain: 'oO',
regex: /((\s)oO|oO(\s|$))/gm,
image: 'Huh.png'
},
{
plain: ':x',
regex: /((\s):x|:x(\s|$))/gm,
image: 'Lips_Sealed.png'
},
{
plain: ':666:',
regex: /((\s):666:|:666:(\s|$))/gm,
image: 'Devil.png'
},
{
plain: '<3',
regex: /((\s)&lt;3|&lt;3(\s|$))/gm,
image: 'Heart.png'
}
],
/** Function: emotify
* Replaces text-emoticons with their image equivalent.
*
* Parameters:
* (String) text - Text to emotify
*
* Returns:
* Emotified text
*/
emotify: function(text) {
var i;
for(i = this.emoticons.length-1; i >= 0; i--) {
text = text.replace(this.emoticons[i].regex, '$2<img class="emoticon" alt="$1" src="' + this._emoticonPath + this.emoticons[i].image + '" />$3');
}
return text;
},
/** Function: linkify
* Replaces URLs with a HTML-link.
*
* Parameters:
* (String) text - Text to linkify
*
* Returns:
* Linkified text
*/
linkify: function(text) {
text = text.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi, '$1http://$2');
return text.replace(/(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/ig, '<a href="$1" target="_blank">$1</a>');
},
/** Function: escape
* Escapes a text using a jQuery function (like htmlspecialchars in PHP)
*
* Parameters:
* (String) text - Text to escape
*
* Returns:
* Escaped text
*/
escape: function(text) {
return $('<div/>').text(text).html();
},
/** Function: all
* Does everything of the parser: escaping, linkifying and emotifying.
*
* Parameters:
* (String) text - Text to parse
*
* Returns:
* Parsed text
*/
all: function(text) {
if(text) {
text = this.escape(text);
text = this.linkify(text);
text = this.emotify(text);
}
return text;
}
};
return self;
}(Candy.Util || {}, jQuery));
/** Class: Candy.Util.Observable
* A class can be extended with the observable to be able to notify observers
*/
Candy.Util.Observable = (function(self) {
/** PrivateObject: _observers
* List of observers
*/
var _observers = {};
/** Function: addObserver
* Add an observer to the observer list
*
* Parameters:
* (String) key - The key the observer listens to
* (Callback) obj - The observer callback
*/
self.addObserver = function(key, obj) {
if (_observers[key] === undefined) {
_observers[key] = [];
}
_observers[key].push(obj);
};
/** Function: deleteObserver
* Delete observer from list
*
* Parameters:
* (String) key - Key in which the observer lies
* (Callback) obj - The observer callback to be deleted
*/
self.deleteObserver = function(key, obj) {
delete _observers[key][obj];
};
/** Function: clearObservers
* Deletes all observers in list
*
* Parameters:
* (String) key - If defined, remove observers of this key, otherwise remove all including all keys.
*/
self.clearObservers = function(key) {
if (key !== undefined) {
_observers[key] = [];
} else {
_observers = {};
}
};
/** Function: notifyObservers
* Notify all of its observers of a certain event.
*
* Parameters:
* (String) key - Key to notify
* (Object) arg - An argument passed to the update-method of the observers
*/
self.notifyObservers = function(key, arg) {
var observer = _observers[key], i;
for(i = observer.length-1; i >= 0; i--) {
observer[i].update(self, arg);
}
};
return self;
}(Candy.Util.Observable || {}));
/** File: view.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View
* The Candy View Class
*
* Parameters:
* (Candy.View) self - itself
* (jQuery) $ - jQuery
*/
Candy.View = (function(self, $) {
/** PrivateObject: _current
* Object containing current container & roomJid which the client sees.
*/
var _current = { container: null, roomJid: null },
/** PrivateObject: _options
*
* Options:
* (String) language - language to use
* (String) resources - path to resources directory (with trailing slash)
* (Object) messages - limit: clean up message pane when n is reached / remove: remove n messages after limit has been reached
* (Object) crop - crop if longer than defined: message.nickname=15, message.body=1000, roster.nickname=15
*/
_options = {
language: 'en',
resources: 'res/',
messages: { limit: 2000, remove: 500 },
crop: {
message: { nickname: 15, body: 1000 },
roster: { nickname: 15 }
}
},
/** PrivateFunction: _setupTranslation
* Set dictionary using jQuery.i18n plugin.
*
* See: view/translation.js
* See: libs/jquery-i18n/jquery.i18n.js
*
* Parameters:
* (String) language - Language identifier
*/
_setupTranslation = function(language) {
$.i18n.setDictionary(self.Translation[language]);
},
/** PrivateFunction: _registerObservers
* Register observers. Candy core will now notify the View on changes.
*/
_registerObservers = function() {
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT, self.Observer.Chat);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.PRESENCE, self.Observer.Presence);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.PRESENCE_ERROR, self.Observer.PresenceError);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.MESSAGE, self.Observer.Message);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.LOGIN, self.Observer.Login);
},
/** PrivateFunction: _registerWindowHandlers
* Register window focus / blur / resize handlers.
*
* jQuery.focus()/.blur() <= 1.5.1 do not work for IE < 9. Fortunately onfocusin/onfocusout will work for them.
*/
_registerWindowHandlers = function() {
// Cross-browser focus handling
if($.browser.msie && !$.browser.version.match('^9')) {
$(document).focusin(Candy.View.Pane.Window.onFocus).focusout(Candy.View.Pane.Window.onBlur);
} else {
$(window).focus(Candy.View.Pane.Window.onFocus).blur(Candy.View.Pane.Window.onBlur);
}
$(window).resize(Candy.View.Pane.Chat.fitTabs);
},
/** PrivateFunction: _registerToolbarHandlers
* Register toolbar handlers and disable sound if cookie says so.
*/
_registerToolbarHandlers = function() {
$('#emoticons-icon').click(function(e) {
self.Pane.Chat.Context.showEmoticonsMenu(e.currentTarget);
e.stopPropagation();
});
$('#chat-autoscroll-control').click(Candy.View.Pane.Chat.Toolbar.onAutoscrollControlClick);
$('#chat-sound-control').click(Candy.View.Pane.Chat.Toolbar.onSoundControlClick);
if(Candy.Util.cookieExists('candy-nosound')) {
$('#chat-sound-control').click();
}
$('#chat-statusmessage-control').click(Candy.View.Pane.Chat.Toolbar.onStatusMessageControlClick);
if(Candy.Util.cookieExists('candy-nostatusmessages')) {
$('#chat-statusmessage-control').click();
}
},
/** PrivateFunction: _delegateTooltips
* Delegate mouseenter on tooltipified element to <Candy.View.Pane.Chat.Tooltip.show>.
*/
_delegateTooltips = function() {
$('body').delegate('li[data-tooltip]', 'mouseenter', Candy.View.Pane.Chat.Tooltip.show);
};
/** Function: init
* Initialize chat view (setup DOM, register handlers & observers)
*
* Parameters:
* (jQuery.element) container - Container element of the whole chat view
* (Object) options - Options: see _options field (value passed here gets extended by the default value in _options field)
*/
self.init = function(container, options) {
$.extend(true, _options, options);
_setupTranslation(_options.language);
// Set path to emoticons
Candy.Util.Parser.setEmoticonPath(this.getOptions().resources + 'img/emoticons/');
// Start DOMination...
_current.container = container;
_current.container.html(Mustache.to_html(Candy.View.Template.Chat.pane, {
tooltipEmoticons : $.i18n._('tooltipEmoticons'),
tooltipSound : $.i18n._('tooltipSound'),
tooltipAutoscroll : $.i18n._('tooltipAutoscroll'),
tooltipStatusmessage : $.i18n._('tooltipStatusmessage'),
tooltipAdministration : $.i18n._('tooltipAdministration'),
tooltipUsercount : $.i18n._('tooltipUsercount'),
resourcesPath : this.getOptions().resources
}, {
tabs: Candy.View.Template.Chat.tabs,
rooms: Candy.View.Template.Chat.rooms,
modal: Candy.View.Template.Chat.modal,
toolbar: Candy.View.Template.Chat.toolbar,
soundcontrol: Candy.View.Template.Chat.soundcontrol
}));
// ... and let the elements dance.
_registerWindowHandlers();
_registerToolbarHandlers();
_registerObservers();
_delegateTooltips();
};
/** Function: getCurrent
* Get current container & roomJid in an object.
*
* Returns:
* Object containing container & roomJid
*/
self.getCurrent = function() {
return _current;
};
/** Function: getOptions
* Gets options
*
* Returns:
* Object
*/
self.getOptions = function() {
return _options;
};
return self;
}(Candy.View || {}, jQuery));
/** File: event.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Event
* Empty hooks to capture events and inject custom code.
*
* Parameters:
* (Candy.View.Event) self - itself
* (jQuery) $ - jQuery
*/
Candy.View.Event = (function(self, $) {
/** Class: Candy.View.Event.Chat
* Chat-related events
*/
self.Chat = {
/** Function: onAdminMessage
* Called when receiving admin messages
*
* Parameters:
* (Object) args - {subject, message}
*/
onAdminMessage: function(args) {
return;
},
/** Function: onDisconnect
* Called when client disconnects
*/
onDisconnect: function() {
return;
},
/** Function: onAuthfail
* Called when authentication fails
*/
onAuthfail: function() {
return;
}
};
/** Class: Candy.View.Event.Room
* Room-related events
*/
self.Room = {
/** Function: onAdd
* Called when a new room gets added
*
* Parameters:
* (Object) args - {roomJid, type=chat|groupchat, element}
*/
onAdd: function(args) {
return;
},
/** Function: onShow
* Called when a room gets shown
*
* Parameters:
* (Object) args - {roomJid, element}
*/
onShow: function(args) {
return;
},
/** Function: onHide
* Called when a room gets hidden
*
* Parameters:
* (Object) args - {roomJid, element}
*/
onHide: function(args) {
return;
},
/** Function: onSubjectChange
* Called when a subject of a room gets changed
*
* Parameters:
* (Object) args - {roomJid, element, subject}
*/
onSubjectChange: function(args) {
return;
},
/** Function: onClose
* Called after a room has been left/closed
*
* Parameters:
* (Object) args - {roomJid}
*/
onClose: function(args) {
return;
},
/** Function: onPresenceChange
* Called when presence of user changes (kick, ban)
*
* Parameters:
* (Object) args - {roomJid, user, reason, type}
*/
onPresenceChange: function(args) {
return;
}
};
/** Class: Candy.View.Event.Roster
* Roster-related events
*/
self.Roster = {
/** Function: onUpdate
* Called after a user have been added to the roster
*
* Parameters:
* (Object) args - {roomJid, user, action, element}
*/
onUpdate: function(args) {
return;
},
/** Function: onContextMenu
* Called when a user clicks on the action menu arrow.
* The return value is getting appended to the menulinks.
*
* Parameters:
* (Object) args - {roomJid, user}
*
* Returns:
* (Object) - containing menulinks
*/
onContextMenu: function(args) {
return {};
},
/** Function: afterContextMenu
* Called when after a the context menu is rendered
*
* Parameters:
* (Object) args - {roomJid, element, user}
*/
afterContextMenu: function(args) {
return;
}
};
/** Class: Candy.View.Event.Message
* Message-related events
*/
self.Message = {
/** Function: beforeShow
* Called before a new message will be shown.
*
* Parameters:
* (Object) args - {roomJid, nick, message}
*
* Returns:
* (String) message
*/
beforeShow: function(args) {
return args.message;
},
/** Function: onShow
* Called after a new message has been shown
*
* Parameters:
* (Object) args - {roomJid, element, nick, message}
*/
onShow: function(args) {
return;
},
/** Function: beforeSend
* Called before a message get sent
*
* Parameters:
* (String) message
*
* Returns:
* (String) message
*/
beforeSend: function(message) {
return message;
}
};
return self;
}(Candy.View.Event || {}, jQuery));
\ No newline at end of file
/** File: observer.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Observer
* Observes Candy core events
*
* Parameters:
* (Candy.View.Observer) self - itself
* (jQuery) $ - jQuery
*/
Candy.View.Observer = (function(self, $) {
/** Class: Candy.View.Observer.Chat
* Chat events
*/
self.Chat = {
/** Function: update
* The update method gets called whenever an event to which "Chat" is subscribed.
*
* Currently listens for connection status updates & admin messages / motd
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {type, connection or message & subject}
*/
update: function(obj, args) {
if(args.type === 'connection') {
switch(args.status) {
case Strophe.Status.CONNECTING:
case Strophe.Status.AUTHENTICATING:
Candy.View.Pane.Chat.Modal.show($.i18n._('statusConnecting'), false, true);
break;
case Strophe.Status.ATTACHED:
case Strophe.Status.CONNECTED:
Candy.View.Pane.Chat.Modal.show($.i18n._('statusConnected'));
Candy.View.Pane.Chat.Modal.hide();
break;
case Strophe.Status.DISCONNECTING:
Candy.View.Pane.Chat.Modal.show($.i18n._('statusDisconnecting'), false, true);
break;
case Strophe.Status.DISCONNECTED:
var presetJid = Candy.Core.isAnonymousConnection() ? Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : null;
Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('statusDisconnected'), presetJid);
Candy.View.Event.Chat.onDisconnect();
break;
case Strophe.Status.AUTHFAIL:
Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('statusAuthfail'));
Candy.View.Event.Chat.onAuthfail();
break;
default:
Candy.View.Pane.Chat.Modal.show($.i18n._('status', args.status));
break;
}
} else if(args.type === 'message') {
Candy.View.Pane.Chat.adminMessage((args.subject || ''), args.message);
} else if(args.type === 'chat' || args.type === 'groupchat') {
// use onInfoMessage as infos from the server shouldn't be hidden by the infoMessage switch.
Candy.View.Pane.Chat.onInfoMessage(Candy.View.getCurrent().roomJid, (args.subject || ''), args.message);
}
}
};
/** Class: Candy.View.Observer.Presence
* Presence update events
*/
self.Presence = {
/** Function: update
* Every presence update gets dispatched from this method.
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - Arguments differ on each type
*
* Uses:
* - <notifyPrivateChats>
*/
update: function(obj, args) {
// Client left
if(args.type === 'leave') {
var user = Candy.View.Pane.Room.getUser(args.roomJid);
Candy.View.Pane.Room.close(args.roomJid);
self.Presence.notifyPrivateChats(user, args.type);
// Client has been kicked or banned
} else if (args.type === 'kick' || args.type === 'ban') {
var actorName = args.actor ? Strophe.getNodeFromJid(args.actor) : null,
actionLabel,
translationParams = [args.roomName];
if (actorName) {
translationParams.push(actorName);
}
switch(args.type) {
case 'kick':
actionLabel = $.i18n._((actorName ? 'youHaveBeenKickedBy' : 'youHaveBeenKicked'), translationParams);
break;
case 'ban':
actionLabel = $.i18n._((actorName ? 'youHaveBeenBannedBy' : 'youHaveBeenBanned'), translationParams);
break;
}
Candy.View.Pane.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.adminMessageReason, {
reason: args.reason,
_action: actionLabel,
_reason: $.i18n._('reasonWas', [args.reason])
}));
setTimeout(function() {
Candy.View.Pane.Chat.Modal.hide(function() {
Candy.View.Pane.Room.close(args.roomJid);
self.Presence.notifyPrivateChats(args.user, args.type);
});
}, 5000);
Candy.View.Event.Room.onPresenceChange({ type: args.type, reason: args.reason, roomJid: args.roomJid, user: args.user });
// A user changed presence
} else {
// Initialize room if not yet existing
if(!Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.Room.init(args.roomJid, args.roomName);
Candy.View.Pane.Room.show(args.roomJid);
}
Candy.View.Pane.Roster.update(args.roomJid, args.user, args.action, args.currentUser);
// Notify private user chats if existing
if(Candy.View.Pane.Chat.rooms[args.user.getJid()]) {
Candy.View.Pane.Roster.update(args.user.getJid(), args.user, args.action, args.currentUser);
Candy.View.Pane.PrivateRoom.setStatus(args.user.getJid(), args.action);
}
}
},
/** Function: notifyPrivateChats
* Notify private user chats if existing
*
* Parameters:
* (Candy.Core.chatUser) user - User which has done the event
* (String) type - Event type (leave, join, kick/ban)
*/
notifyPrivateChats: function(user, type) {
Candy.Core.log('[View:Observer] notify Private Chats');
var roomJid;
for(roomJid in Candy.View.Pane.Chat.rooms) {
if(Candy.View.Pane.Chat.rooms.hasOwnProperty(roomJid) && Candy.View.Pane.Room.getUser(roomJid) && user.getJid() === Candy.View.Pane.Room.getUser(roomJid).getJid()) {
Candy.View.Pane.Roster.update(roomJid, user, type, user);
Candy.View.Pane.PrivateRoom.setStatus(roomJid, type);
}
}
}
};
/** Class: Candy.View.Observer.PresenceError
* Presence error events
*/
self.PresenceError = {
/** Function: update
* Presence errors get handled in this method
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {msg, type, roomJid, roomName}
*/
update: function(obj, args) {
switch(args.type) {
case 'not-authorized':
var message;
if (args.msg.children('x').children('password').length > 0) {
message = $.i18n._('passwordEnteredInvalid', [args.roomName]);
}
Candy.View.Pane.Chat.Modal.showEnterPasswordForm(args.roomJid, args.roomName, message);
break;
case 'conflict':
Candy.View.Pane.Chat.Modal.showNicknameConflictForm(args.roomJid);
break;
case 'registration-required':
Candy.View.Pane.Chat.Modal.showError('errorMembersOnly', [args.roomName]);
break;
case 'service-unavailable':
Candy.View.Pane.Chat.Modal.showError('errorMaxOccupantsReached', [args.roomName]);
break;
}
}
}
/** Class: Candy.View.Observer.Message
* Message related events
*/
self.Message = {
/** Function: update
* Messages received get dispatched from this method.
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {message, roomJid}
*/
update: function(obj, args) {
if(args.message.type === 'subject') {
if (!Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.Room.init(args.roomJid, args.message.name);
Candy.View.Pane.Room.show(args.roomJid);
}
Candy.View.Pane.Room.setSubject(args.roomJid, args.message.body);
} else if(args.message.type === 'info') {
Candy.View.Pane.Chat.infoMessage(args.roomJid, args.message.body);
} else {
// Initialize room if it's a message for a new private user chat
if(args.message.type === 'chat' && !Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.PrivateRoom.open(args.roomJid, args.message.name, false, args.message.isNoConferenceRoomJid);
}
Candy.View.Pane.Message.show(args.roomJid, args.message.name, args.message.body, args.timestamp);
}
}
};
/** Class: Candy.View.Observer.Login
* Handles when display login window should appear
*/
self.Login = {
/** Function: update
* The login event gets dispatched to this method
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {presetJid}
*/
update: function(obj, args) {
Candy.View.Pane.Chat.Modal.showLoginForm(null, args.presetJid);
}
};
return self;
}(Candy.View.Observer || {}, jQuery));
\ No newline at end of file
/** File: pane.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Pane
* Candy view pane handles everything regarding DOM updates etc.
*
* Parameters:
* (Candy.View.Pane) self - itself
* (jQuery) $ - jQuery
*/
Candy.View.Pane = (function(self, $) {
/** Class: Candy.View.Pane.Window
* Window related view updates
*/
self.Window = {
/** PrivateVariable: _hasFocus
* Window has focus
*/
_hasFocus: true,
/** PrivateVariable: _plainTitle
* Document title
*/
_plainTitle: document.title,
/** PrivateVariable: _unreadMessagesCount
* Unread messages count
*/
_unreadMessagesCount: 0,
/** Variable: autoscroll
* Boolean whether autoscroll is enabled
*/
autoscroll: true,
/** Function: hasFocus
* Checks if window has focus
*
* Returns:
* (Boolean)
*/
hasFocus: function() {
return self.Window._hasFocus;
},
/** Function: increaseUnreadMessages
* Increases unread message count in window title by one.
*/
increaseUnreadMessages: function() {
self.Window.renderUnreadMessages(++self.Window._unreadMessagesCount);
},
/** Function: reduceUnreadMessages
* Reduce unread message count in window title by `num`.
*
* Parameters:
* (Integer) num - Unread message count will be reduced by this value
*/
reduceUnreadMessages: function(num) {
self.Window._unreadMessagesCount -= num;
if(self.Window._unreadMessagesCount <= 0) {
self.Window.clearUnreadMessages();
} else {
self.Window.renderUnreadMessages(self.Window._unreadMessagesCount);
}
},
/** Function: clearUnreadMessages
* Clear unread message count in window title.
*/
clearUnreadMessages: function() {
self.Window._unreadMessagesCount = 0;
document.title = self.Window._plainTitle;
},
/** Function: renderUnreadMessages
* Update window title to show message count.
*
* Parameters:
* (Integer) count - Number of unread messages to show in window title
*/
renderUnreadMessages: function(count) {
document.title = Candy.View.Template.Window.unreadmessages.replace('{{count}}', count).replace('{{title}}', self.Window._plainTitle);
},
/** Function: onFocus
* Window focus event handler.
*/
onFocus: function() {
self.Window._hasFocus = true;
if (Candy.View.getCurrent().roomJid) {
self.Room.setFocusToForm(Candy.View.getCurrent().roomJid);
self.Chat.clearUnreadMessages(Candy.View.getCurrent().roomJid);
}
},
/** Function: onBlur
* Window blur event handler.
*/
onBlur: function() {
self.Window._hasFocus = false;
}
};
/** Class: Candy.View.Pane.Chat
* Chat-View related view updates
*/
self.Chat = {
/** Variable: rooms
* Contains opened room elements
*/
rooms: [],
/** Function: addTab
* Add a tab to the chat pane.
*
* Parameters:
* (String) roomJid - JID of room
* (String) roomName - Tab label
* (String) roomType - Type of room: `groupchat` or `chat`
*/
addTab: function(roomJid, roomName, roomType) {
var roomId = Candy.Util.jidToId(roomJid),
html = Mustache.to_html(Candy.View.Template.Chat.tab, {
roomJid: roomJid,
roomId: roomId,
name: roomName || Strophe.getNodeFromJid(roomJid),
privateUserChat: function() { return roomType === 'chat'; },
roomType: roomType
}),
tab = $(html).appendTo('#chat-tabs');
tab.click(self.Chat.tabClick);
// TODO: maybe we find a better way to get the close element.
$('a.close', tab).click(self.Chat.tabClose);
self.Chat.fitTabs();
},
/** Function: getTab
* Get tab by JID.
*
* Parameters:
* (String) roomJid - JID of room
*
* Returns:
* (jQuery object) - Tab element
*/
getTab: function(roomJid) {
return $('#chat-tabs').children('li[data-roomjid="' + roomJid + '"]');
},
/** Function: removeTab
* Remove tab element.
*
* Parameters:
* (String) roomJid - JID of room
*/
removeTab: function(roomJid) {
self.Chat.getTab(roomJid).remove();
self.Chat.fitTabs();
},
/** Function: setActiveTab
* Set the active tab.
*
* Add CSS classname `active` to the choosen tab and remove `active` from all other.
*
* Parameters:
* (String) roomJid - JID of room
*/
setActiveTab: function(roomJid) {
$('#chat-tabs').children().each(function() {
var tab = $(this);
if(tab.attr('data-roomjid') === roomJid) {
tab.addClass('active');
} else {
tab.removeClass('active');
}
});
},
/** Function: increaseUnreadMessages
* Increase unread message count in a tab by one.
*
* Parameters:
* (String) roomJid - JID of room
*
* Uses:
* - <Window.increaseUnreadMessages>
*/
increaseUnreadMessages: function(roomJid) {
var unreadElem = this.getTab(roomJid).find('.unread');
unreadElem.show().text(unreadElem.text() !== '' ? parseInt(unreadElem.text(), 10) + 1 : 1);
// only increase window unread messages in private chats
if (self.Chat.rooms[roomJid].type === 'chat') {
self.Window.increaseUnreadMessages();
}
},
/** Function: clearUnreadMessages
* Clear unread message count in a tab.
*
* Parameters:
* (String) roomJid - JID of room
*
* Uses:
* - <Window.reduceUnreadMessages>
*/
clearUnreadMessages: function(roomJid) {
var unreadElem = self.Chat.getTab(roomJid).find('.unread');
self.Window.reduceUnreadMessages(unreadElem.text());
unreadElem.hide().text('');
},
/** Function: tabClick
* Tab click event: show the room associated with the tab and stops the event from doing the default.
*/
tabClick: function(e) {
// remember scroll position of current room
var currentRoomJid = Candy.View.getCurrent().roomJid;
self.Chat.rooms[currentRoomJid].scrollPosition = self.Room.getPane(currentRoomJid, '.message-pane-wrapper').scrollTop();
self.Room.show($(this).attr('data-roomjid'));
e.preventDefault();
},
/** Function: tabClose
* Tab close (click) event: Leave the room (groupchat) or simply close the tab (chat).
*
* Parameters:
* (DOMEvent) e - Event triggered
*
* Returns:
* (Boolean) - false, this will stop the event from bubbling
*/
tabClose: function(e) {
var roomJid = $(this).parent().attr('data-roomjid');
// close private user tab
if(self.Chat.rooms[roomJid].type === 'chat') {
self.Room.close(roomJid);
// close multi-user room tab
} else {
Candy.Core.Action.Jabber.Room.Leave(roomJid);
}
return false;
},
/** Function: allTabsClosed
* All tabs closed event: Disconnect from service. Hide sound control.
*
* TODO: Handle window close
*
* Returns:
* (Boolean) - false, this will stop the event from bubbling
*/
allTabsClosed: function() {
Candy.Core.disconnect();
self.Chat.Toolbar.hide();
return;
// this is a workaround because browsers prevent to close non-js-opened windows
/*if($.browser.msie) {
this.focus();
self.opener = this;
self.close();
} else {
window.open(location.href, '_self');
window.close();
}*/
},
/** Function: fitTabs
* Fit tab size according to window size
*/
fitTabs: function() {
var availableWidth = $('#chat-tabs').innerWidth(),
tabsWidth = 0,
tabs = $('#chat-tabs').children();
tabs.each(function() {
tabsWidth += $(this).css({ width: 'auto', overflow: 'visible' }).outerWidth(true);
});
if(tabsWidth > availableWidth) {
// tabs.[outer]Width() measures the first element in `tabs`. It's no very readable but nearly two times faster than using :first
var tabDiffToRealWidth = tabs.outerWidth(true) - tabs.width(),
tabWidth = Math.floor((availableWidth) / tabs.length) - tabDiffToRealWidth;
tabs.css({ width: tabWidth, overflow: 'hidden' });
}
},
/** Function: updateToolbar
* Show toolbar
*/
updateToolbar: function(roomJid) {
$('#chat-toolbar').find('.context').click(function(e) {
self.Chat.Context.show(e.currentTarget, roomJid);
e.stopPropagation();
});
Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
},
/** Function: adminMessage
* Display admin message
*
* Parameters:
* (String) subject - Admin message subject
* (String) message - Message to be displayed
*/
adminMessage: function(subject, message) {
if(Candy.View.getCurrent().roomJid) { // Simply dismiss admin message if no room joined so far. TODO: maybe we should show those messages on a dedicated pane?
var html = Mustache.to_html(Candy.View.Template.Chat.adminMessage, {
subject: subject,
message: message,
sender: $.i18n._('administratorMessageSubject'),
time: Candy.Util.localizedTime(new Date().toGMTString())
});
$('#chat-rooms').children().each(function() {
self.Room.appendToMessagePane($(this).attr('data-roomjid'), html);
});
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
Candy.View.Event.Chat.onAdminMessage({'subject' : subject, 'message' : message});
}
},
/** Function: infoMessage
* Display info message. This is a wrapper for <onInfoMessage> to be able to disable certain info messages.
*
* Parameters:
* (String) roomJid - Room JID
* (String) subject - Subject
* (String) message - Message
*/
infoMessage: function(roomJid, subject, message) {
self.Chat.onInfoMessage(roomJid, subject, message);
},
/** Function: onInfoMessage
* Display info message. Used by <infoMessage> and several other functions which do not wish that their info message
* can be disabled (such as kick/ban message or leave/join message in private chats).
*
* Parameters:
* (String) roomJid - Room JID
* (String) subject - Subject
* (String) message - Message
*/
onInfoMessage: function(roomJid, subject, message) {
if(Candy.View.getCurrent().roomJid) { // Simply dismiss info message if no room joined so far. TODO: maybe we should show those messages on a dedicated pane?
var html = Mustache.to_html(Candy.View.Template.Chat.infoMessage, {
subject: subject,
message: $.i18n._(message),
time: Candy.Util.localizedTime(new Date().toGMTString())
});
self.Room.appendToMessagePane(roomJid, html);
if (Candy.View.getCurrent().roomJid === roomJid) {
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
}
}
},
/** Class: Candy.View.Pane.Toolbar
* Chat toolbar for things like emoticons toolbar, room management etc.
*/
Toolbar: {
/** Function: show
* Show toolbar.
*/
show: function() {
$('#chat-toolbar').show();
},
/** Function: hide
* Hide toolbar.
*/
hide: function() {
$('#chat-toolbar').hide();
},
/** Function: playSound
* Play sound (default method).
*/
playSound: function() {
self.Chat.Toolbar.onPlaySound();
},
/** Function: onPlaySound
* Sound play event handler.
*
* Don't call this method directly. Call `playSound()` instead.
* `playSound()` will only call this method if sound is enabled.
*/
onPlaySound: function() {
var chatSoundPlayer = document.getElementById('chat-sound-player');
chatSoundPlayer.SetVariable('method:stop', '');
chatSoundPlayer.SetVariable('method:play', '');
},
/** Function: onSoundControlClick
* Sound control click event handler.
*
* Toggle sound (overwrite `playSound()`) and handle cookies.
*/
onSoundControlClick: function() {
var control = $('#chat-sound-control');
if(control.hasClass('checked')) {
self.Chat.Toolbar.playSound = function() {};
Candy.Util.setCookie('candy-nosound', '1', 365);
} else {
self.Chat.Toolbar.playSound = function() {
self.Chat.Toolbar.onPlaySound();
};
Candy.Util.deleteCookie('candy-nosound');
}
control.toggleClass('checked');
},
/** Function: onAutoscrollControlClick
* Autoscroll control event handler.
*
* Toggle autoscroll
*/
onAutoscrollControlClick: function() {
var control = $('#chat-autoscroll-control');
if(control.hasClass('checked')) {
self.Room.scrollToBottom = function(roomJid) {
self.Room.onScrollToStoredPosition(roomJid);
};
self.Window.autoscroll = false;
} else {
self.Room.scrollToBottom = function(roomJid) {
self.Room.onScrollToBottom(roomJid);
};
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
self.Window.autoscroll = true;
}
control.toggleClass('checked');
},
/** Function: onStatusMessageControlClick
* Status message control event handler.
*
* Toggle status message
*/
onStatusMessageControlClick: function() {
var control = $('#chat-statusmessage-control');
if(control.hasClass('checked')) {
self.Chat.infoMessage = function() {};
Candy.Util.setCookie('candy-nostatusmessages', '1', 365);
} else {
self.Chat.infoMessage = function(roomJid, subject, message) {
self.Chat.onInfoMessage(roomJid, subject, message);
};
Candy.Util.deleteCookie('candy-nostatusmessages');
}
control.toggleClass('checked');
},
/** Function: updateUserCount
* Update usercount element with count.
*
* Parameters:
* (Integer) count - Current usercount
*/
updateUsercount: function(count) {
$('#chat-usercount').text(count);
}
},
/** Class: Candy.View.Pane.Modal
* Modal window
*/
Modal: {
/** Function: show
* Display modal window
*
* Parameters:
* (String) html - HTML code to put into the modal window
* (Boolean) showCloseControl - set to true if a close button should be displayed [default false]
* (Boolean) showSpinner - set to true if a loading spinner should be shown [default false]
*/
show: function(html, showCloseControl, showSpinner) {
if(showCloseControl) {
self.Chat.Modal.showCloseControl();
} else {
self.Chat.Modal.hideCloseControl();
}
if(showSpinner) {
self.Chat.Modal.showSpinner();
} else {
self.Chat.Modal.hideSpinner();
}
$('#chat-modal').stop(false, true);
$('#chat-modal-body').html(html);
$('#chat-modal').fadeIn('fast');
$('#chat-modal-overlay').show();
},
/** Function: hide
* Hide modal window
*
* Parameters:
* (Function) callback - Calls the specified function after modal window has been hidden.
*/
hide: function(callback) {
$('#chat-modal').fadeOut('fast', function() {
$('#chat-modal-body').text('');
$('#chat-modal-overlay').hide();
});
// restore initial esc handling
$(document).keydown(function(e) {
if(e.which === 27) {
e.preventDefault();
}
});
if (callback) {
callback();
}
},
/** Function: showSpinner
* Show loading spinner
*/
showSpinner: function() {
$('#chat-modal-spinner').show();
},
/** Function: hideSpinner
* Hide loading spinner
*/
hideSpinner: function() {
$('#chat-modal-spinner').hide();
},
/** Function: showCloseControl
* Show a close button
*/
showCloseControl: function() {
$('#admin-message-cancel').show().click(function(e) {
self.Chat.Modal.hide();
// some strange behaviour on IE7 (and maybe other browsers) triggers onWindowUnload when clicking on the close button.
// prevent this.
e.preventDefault();
});
// enable esc to close modal
$(document).keydown(function(e) {
if(e.which === 27) {
self.Chat.Modal.hide();
e.preventDefault();
}
});
},
/** Function: hideCloseControl
* Hide the close button
*/
hideCloseControl: function() {
$('#admin-message-cancel').hide().click(function() {});
},
/** Function: showLoginForm
* Show the login form modal
*
* Parameters:
* (String) message - optional message to display above the form
* (String) presetJid - optional user jid. if set, the user will only be prompted for password.
*/
showLoginForm: function(message, presetJid) {
self.Chat.Modal.show((message ? message : '') + Mustache.to_html(Candy.View.Template.Login.form, {
_labelUsername: $.i18n._('labelUsername'),
_labelPassword: $.i18n._('labelPassword'),
_loginSubmit: $.i18n._('loginSubmit'),
displayPassword: !Candy.Core.isAnonymousConnection(),
displayUsername: Candy.Core.isAnonymousConnection() || !presetJid,
presetJid: presetJid ? presetJid : false
}));
$('#login-form').children()[0].focus();
// register submit handler
$('#login-form').submit(function(event) {
var username = $('#username').val(),
password = $('#password').val();
if (!Candy.Core.isAnonymousConnection()) {
// guess the input and create a jid out of it
var jid = Candy.Core.getUser() && username.indexOf("@") < 0 ?
username + '@' + Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : username;
if(jid.indexOf("@") < 0 && !Candy.Core.getUser()) {
Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('loginInvalid'));
} else {
//Candy.View.Pane.Chat.Modal.hide();
Candy.Core.connect(jid, password);
}
} else { // anonymous login
Candy.Core.connect(presetJid, null, username);
}
return false;
});
},
/** Function: showEnterPasswordForm
* Shows a form for entering room password
*
* Parameters:
* (String) roomJid - Room jid to join
* (String) roomName - Room name
* (String) message - [optional] Message to show as the label
*/
showEnterPasswordForm: function(roomJid, roomName, message) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.enterPasswordForm, {
roomName: roomName,
_labelPassword: $.i18n._('labelPassword'),
_label: (message ? message : $.i18n._('enterRoomPassword', [roomName])),
_joinSubmit: $.i18n._('enterRoomPasswordSubmit')
}), true);
$('#password').focus();
// register submit handler
$('#enter-password-form').submit(function() {
var password = $('#password').val();
self.Chat.Modal.hide(function() {
Candy.Core.Action.Jabber.Room.Join(roomJid, password);
});
return false;
});
},
/** Function: showNicknameConflictForm
* Shows a form indicating that the nickname is already taken and
* for chosing a new nickname
*
* Parameters:
* (String) roomJid - Room jid to join
*/
showNicknameConflictForm: function(roomJid) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.nicknameConflictForm, {
_labelNickname: $.i18n._('labelUsername'),
_label: $.i18n._('nicknameConflict'),
_loginSubmit: $.i18n._('loginSubmit')
}));
$('#nickname').focus();
// register submit handler
$('#nickname-conflict-form').submit(function() {
var nickname = $('#nickname').val();
self.Chat.Modal.hide(function() {
Candy.Core.getUser().data.nick = nickname;
Candy.Core.Action.Jabber.Room.Join(roomJid);
});
return false;
});
},
/** Function: showError
* Show modal containing error message
*
* Parameters:
* (String) message - key of translation to display
* (Array) replacements - array containing replacements for translation (%s)
*/
showError: function(message, replacements) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.displayError, {
_error: $.i18n._(message, replacements)
}), true);
}
},
/** Class: Candy.View.Pane.Tooltip
* Class to display tooltips over specific elements
*/
Tooltip: {
/** Function: show
* Show a tooltip on event.currentTarget with content specified or content within the target's attribute data-tooltip.
*
* On mouseleave on the target, hide the tooltip.
*
* Parameters:
* (Event) event - Triggered event
* (String) content - Content to display [optional]
*/
show: function(event, content) {
var tooltip = $('#tooltip'),
target = $(event.currentTarget);
if(!content) {
content = target.attr('data-tooltip');
}
if(tooltip.length === 0) {
var html = Mustache.to_html(Candy.View.Template.Chat.tooltip);
$('#chat-pane').append(html);
tooltip = $('#tooltip');
}
$('#context-menu').hide();
tooltip.stop(false, true);
tooltip.children('div').html(content);
var pos = target.offset(),
posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(tooltip, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(tooltip, pos.top);
tooltip.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment}).fadeIn('fast');
target.mouseleave(function(event) {
event.stopPropagation();
$('#tooltip').stop(false, true).fadeOut('fast', function() { $(this).css({'top': 0, 'left': 0}); });
});
}
},
/** Class: Candy.View.Pane.Context
* Context menu for actions and settings
*/
Context: {
/** Function: init
* Initialize context menu and setup mouseleave handler.
*/
init: function() {
if ($('#context-menu').length === 0) {
var html = Mustache.to_html(Candy.View.Template.Chat.Context.menu);
$('#chat-pane').append(html);
$('#context-menu').mouseleave(function() {
$(this).fadeOut('fast');
});
}
},
/** Function: show
* Show context menu (positions it according to the window height/width)
*
* Uses:
* <getMenuLinks> for getting menulinks the user has access to
* <Candy.Util.getPosLeftAccordingToWindowBounds> for positioning
* <Candy.Util.getPosTopAccordingToWindowBounds> for positioning
*
* Calls:
* <Candy.View.Event.Roster.afterContextMenu> after showing the context menu
*
* Parameters:
* (Element) elem - On which element it should be shown
* (String) roomJid - Room Jid of the room it should be shown
* (Candy.Core.chatUser) user - User
*/
show: function(elem, roomJid, user) {
elem = $(elem);
var roomId = self.Chat.rooms[roomJid].id,
menu = $('#context-menu'),
links = $('ul li', menu);
$('#tooltip').hide();
// add specific context-user class if a user is available (when context menu should be opened next to a user)
if(!user) {
user = Candy.Core.getUser();
}
links.remove();
var menulinks = this.getMenuLinks(roomJid, user, elem),
id,
clickHandler = function(roomJid, user) {
return function(event) {
event.data.callback(event, roomJid, user);
$('#context-menu').hide();
};
};
for(id in menulinks) {
if(menulinks.hasOwnProperty(id)) {
var link = menulinks[id],
html = Mustache.to_html(Candy.View.Template.Chat.Context.menulinks, {
'roomId' : roomId,
'class' : link['class'],
'id' : id,
'label' : link.label
});
$('ul', menu).append(html);
$('#context-menu-' + id).bind('click', link, clickHandler(roomJid, user));
}
}
// if `id` is set the menu is not empty
if(id) {
var pos = elem.offset(),
posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
menu.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment});
menu.fadeIn('fast');
Candy.View.Event.Roster.afterContextMenu({'roomJid' : roomJid, 'user' : user, 'element': menu });
return true;
}
},
/** Function: getMenuLinks
* Extends <initialMenuLinks> with <Candy.View.Event.Roster.onContextMenu> links and returns those.
*
* Returns:
* (Object) - object containing the extended menulinks.
*/
getMenuLinks: function(roomJid, user, elem) {
var menulinks = $.extend(this.initialMenuLinks(elem), Candy.View.Event.Roster.onContextMenu({'roomJid' : roomJid, 'user' : user, 'elem': elem })),
id;
for(id in menulinks) {
if(menulinks.hasOwnProperty(id) && menulinks[id].requiredPermission !== undefined && !menulinks[id].requiredPermission(user, self.Room.getUser(roomJid), elem)) {
delete menulinks[id];
}
}
return menulinks;
},
/** Function: initialMenuLinks
* Returns initial menulinks. The following are initial:
*
* - Private Chat
* - Ignore
* - Unignore
* - Kick
* - Ban
* - Change Subject
*
* Returns:
* (Object) - object containing those menulinks
*/
initialMenuLinks: function() {
return {
'private': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && Candy.Core.getRoom(Candy.View.getCurrent().roomJid) && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
},
'class' : 'private',
'label' : $.i18n._('privateActionLabel'),
'callback' : function(e, roomJid, user) {
$('#user-' + Candy.Util.jidToId(roomJid) + '-' + Candy.Util.jidToId(user.getJid())).click();
}
},
'ignore': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
},
'class' : 'ignore',
'label' : $.i18n._('ignoreActionLabel'),
'callback' : function(e, roomJid, user) {
Candy.View.Pane.Room.ignoreUser(roomJid, user.getJid());
}
},
'unignore': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
},
'class' : 'unignore',
'label' : $.i18n._('unignoreActionLabel'),
'callback' : function(e, roomJid, user) {
Candy.View.Pane.Room.unignoreUser(roomJid, user.getJid());
}
},
'kick': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
},
'class' : 'kick',
'label' : $.i18n._('kickActionLabel'),
'callback' : function(e, roomJid, user) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
_label: $.i18n._('reason'),
_submit: $.i18n._('kickActionLabel')
}), true);
$('#context-modal-field').focus();
$('#context-modal-form').submit(function(event) {
Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'kick', $('#context-modal-field').val());
self.Chat.Modal.hide();
return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
});
}
},
'ban': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
},
'class' : 'ban',
'label' : $.i18n._('banActionLabel'),
'callback' : function(e, roomJid, user) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
_label: $.i18n._('reason'),
_submit: $.i18n._('banActionLabel')
}), true);
$('#context-modal-field').focus();
$('#context-modal-form').submit(function(e) {
Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'ban', $('#context-modal-field').val());
self.Chat.Modal.hide();
return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
});
}
},
'subject': {
requiredPermission: function(user, me) {
return me.getNick() === user.getNick() && me.isModerator();
},
'class': 'subject',
'label' : $.i18n._('setSubjectActionLabel'),
'callback': function(e, roomJid, user) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
_label: $.i18n._('subject'),
_submit: $.i18n._('setSubjectActionLabel')
}), true);
$('#context-modal-field').focus();
$('#context-modal-form').submit(function(e) {
Candy.Core.Action.Jabber.Room.Admin.SetSubject(roomJid, $('#context-modal-field').val());
self.Chat.Modal.hide();
e.preventDefault();
});
}
}
};
},
/** Function: showEmoticonsMenu
* Shows the special emoticons menu
*
* Parameters:
* (Element) elem - Element on which it should be positioned to.
*
* Returns:
* (Boolean) - true
*/
showEmoticonsMenu: function(elem) {
elem = $(elem);
var pos = elem.offset(),
menu = $('#context-menu'),
content = $('ul', menu),
emoticons = '',
i;
$('#tooltip').hide();
for(i = Candy.Util.Parser.emoticons.length-1; i >= 0; i--) {
emoticons = '<img src="' + Candy.Util.Parser._emoticonPath + Candy.Util.Parser.emoticons[i].image + '" alt="' + Candy.Util.Parser.emoticons[i].plain + '" />' + emoticons;
}
content.html('<li class="emoticons">' + emoticons + '</li>');
content.find('img').click(function() {
var input = Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid, '.message-form').children('.field'),
value = input.val(),
emoticon = $(this).attr('alt') + ' ';
input.val(value ? value + ' ' + emoticon : emoticon).focus();
});
var posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
menu.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment});
menu.fadeIn('fast');
return true;
}
}
};
/** Class: Candy.View.Pane.Room
* Everything which belongs to room view things belongs here.
*/
self.Room = {
/** Function: init
* Initialize a new room and inserts the room html into the DOM
*
* Parameters:
* (String) roomJid - Room JID
* (String) roomName - Room name
* (String) roomType - Type: either "groupchat" or "chat" (private chat)
*
* Uses:
* - <Candy.Util.jidToId>
* - <Candy.View.Pane.Chat.addTab>
* - <getPane>
*
* Calls:
* - <Candy.View.Event.Room.onAdd>
*
* Returns:
* (String) - the room id of the element created.
*/
init: function(roomJid, roomName, roomType) {
roomType = roomType || 'groupchat';
// First room, show sound control
if(Candy.Util.isEmptyObject(self.Chat.rooms)) {
self.Chat.Toolbar.show();
}
var roomId = Candy.Util.jidToId(roomJid);
self.Chat.rooms[roomJid] = { id: roomId, usercount: 0, name: roomName, type: roomType, messageCount: 0, scrollPosition: -1 };
$('#chat-rooms').append(Mustache.to_html(Candy.View.Template.Room.pane, {
roomId: roomId,
roomJid: roomJid,
roomType: roomType,
form: {
_messageSubmit: $.i18n._('messageSubmit')
},
roster: {
_userOnline: $.i18n._('userOnline')
}
}, {
roster: Candy.View.Template.Roster.pane,
messages: Candy.View.Template.Message.pane,
form: Candy.View.Template.Room.form
}));
self.Chat.addTab(roomJid, roomName, roomType);
self.Room.getPane(roomJid, '.message-form').submit(self.Message.submit);
Candy.View.Event.Room.onAdd({'roomJid': roomJid, 'type': roomType, 'element': self.Room.getPane(roomJid)});
return roomId;
},
/** Function: show
* Show a specific room and hides the other rooms (if there are any)
*
* Parameters:
* (String) roomJid - room jid to show
*/
show: function(roomJid) {
var roomId = self.Chat.rooms[roomJid].id;
$('.room-pane').each(function() {
var elem = $(this);
if(elem.attr('id') === ('chat-room-' + roomId)) {
elem.show();
Candy.View.getCurrent().roomJid = roomJid;
self.Chat.updateToolbar(roomJid);
self.Chat.setActiveTab(roomJid);
self.Chat.clearUnreadMessages(roomJid);
self.Room.setFocusToForm(roomJid);
self.Room.scrollToBottom(roomJid);
Candy.View.Event.Room.onShow({'roomJid': roomJid, 'element' : elem});
} else {
elem.hide();
Candy.View.Event.Room.onHide({'roomJid': roomJid, 'element' : elem});
}
});
},
/** Function: setSubject
* Called when someone changes the subject in the channel
*
* Parameters:
* (String) roomJid - Room Jid
* (String) subject - The new subject
*/
setSubject: function(roomJid, subject) {
var html = Mustache.to_html(Candy.View.Template.Room.subject, {
subject: subject,
roomName: self.Chat.rooms[roomJid].name,
_roomSubject: $.i18n._('roomSubject'),
time: Candy.Util.localizedTime(new Date().toGMTString())
});
self.Room.appendToMessagePane(roomJid, html);
self.Room.scrollToBottom(roomJid);
Candy.View.Event.Room.onSubjectChange({'roomJid': roomJid, 'element' : self.Room.getPane(roomJid), 'subject' : subject});
},
/** Function: close
* Close a room and remove everything in the DOM belonging to this room.
*
* NOTICE: There's a rendering bug in Opera when all rooms have been closed. (Take a look in the source for a more detailed description)
*
* Parameters:
* (String) roomJid - Room to close
*/
close: function(roomJid) {
self.Chat.removeTab(roomJid);
self.Window.clearUnreadMessages();
/* TODO:
There's a rendering bug in Opera which doesn't redraw (remove) the message form.
Only a cosmetical issue (when all tabs are closed) but it's annoying...
This happens when form has no focus too. Maybe it's because of CSS positioning.
*/
self.Room.getPane(roomJid).remove();
var openRooms = $('#chat-rooms').children();
if(Candy.View.getCurrent().roomJid === roomJid) {
Candy.View.getCurrent().roomJid = null;
if(openRooms.length === 0) {
self.Chat.allTabsClosed();
} else {
self.Room.show(openRooms.last().attr('data-roomjid'));
}
}
delete self.Chat.rooms[roomJid];
Candy.View.Event.Room.onClose({'roomJid' : roomJid});
},
/** Function: appendToMessagePane
* Append a new message to the message pane.
*
* Parameters:
* (String) roomJid - Room JID
* (String) html - rendered message html
*/
appendToMessagePane: function(roomJid, html) {
self.Room.getPane(roomJid, '.message-pane').append(html);
self.Chat.rooms[roomJid].messageCount++;
self.Room.sliceMessagePane(roomJid);
},
/** Function: sliceMessagePane
* Slices the message pane after the max amount of messages specified in the Candy View options (limit setting).
*
* This is done to hopefully prevent browsers from getting slow after a certain amount of messages in the DOM.
*
* The slice is only done when autoscroll is on, because otherwise someone might lose exactly the message he want to look for.
*
* Parameters:
* (String) roomJid - Room JID
*/
sliceMessagePane: function(roomJid) {
// Only clean if autoscroll is enabled
if(self.Window.autoscroll) {
var options = Candy.View.getOptions().messages;
if(self.Chat.rooms[roomJid].messageCount > options.limit) {
self.Room.getPane(roomJid, '.message-pane').children().slice(0, options.remove*2).remove();
self.Chat.rooms[roomJid].messageCount -= options.remove;
}
}
},
/** Function: scrollToBottom
* Scroll to bottom wrapper for <onScrollToBottom> to be able to disable it by overwriting the function.
*
* Parameters:
* (String) roomJid - Room JID
*
* Uses:
* - <onScrollToBottom>
*/
scrollToBottom: function(roomJid) {
self.Room.onScrollToBottom(roomJid);
},
/** Function: onScrollToBottom
* Scrolls to the latest message received/sent.
*
* Parameters:
* (String) roomJid - Room JID
*/
onScrollToBottom: function(roomJid) {
var messagePane = self.Room.getPane(roomJid, '.message-pane-wrapper');
messagePane.scrollTop(messagePane.prop('scrollHeight'));
},
/** Function: onScrollToStoredPosition
* When autoscroll is off, the position where the scrollbar is has to be stored for each room, because it otherwise
* goes to the top in the message window.
*
* Parameters:
* (String) roomJid - Room JID
*/
onScrollToStoredPosition: function(roomJid) {
// This should only apply when entering a room...
// ... therefore we set scrollPosition to -1 after execution.
if(self.Chat.rooms[roomJid].scrollPosition > -1) {
var messagePane = self.Room.getPane(roomJid, '.message-pane-wrapper');
messagePane.scrollTop(self.Chat.rooms[roomJid].scrollPosition);
self.Chat.rooms[roomJid].scrollPosition = -1;
}
},
/** Function: setFocusToForm
* Set focus to the message input field within the message form.
*
* Parameters:
* (String) roomJid - Room JID
*/
setFocusToForm: function(roomJid) {
var pane = self.Room.getPane(roomJid, '.message-form');
if (pane) {
// IE8 will fail maybe, because the field isn't there yet.
try {
pane.children('.field')[0].focus();
} catch(e) {
// fail silently
}
}
},
/** Function: setUser
* Sets or updates the current user in the specified room (called by <Candy.View.Pane.Roster.update>) and set specific informations
* (roles and affiliations) on the room tab (chat-pane).
*
* Parameters:
* (String) roomJid - Room in which the user is set to.
* (Candy.Core.ChatUser) user - The user
*/
setUser: function(roomJid, user) {
self.Chat.rooms[roomJid].user = user;
var roomPane = self.Room.getPane(roomJid),
chatPane = $('#chat-pane');
roomPane.attr('data-userjid', user.getJid());
// Set classes based on user role / affiliation
if(user.isModerator()) {
if (user.getRole() === user.ROLE_MODERATOR) {
chatPane.addClass('role-moderator');
}
if (user.getAffiliation() === user.AFFILIATION_OWNER) {
chatPane.addClass('affiliation-owner');
}
} else {
chatPane.removeClass('role-moderator affiliation-owner');
}
self.Chat.Context.init();
},
/** Function: getUser
* Get the current user in the room specified with the jid
*
* Parameters:
* (String) roomJid - Room of which the user should be returned from
*
* Returns:
* (Candy.Core.ChatUser) - user
*/
getUser: function(roomJid) {
return self.Chat.rooms[roomJid].user;
},
/** Function: ignoreUser
* Ignore specified user and add the ignore icon to the roster item of the user
*
* Parameters:
* (String) roomJid - Room in which the user should be ignored
* (String) userJid - User which should be ignored
*/
ignoreUser: function(roomJid, userJid) {
Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
Candy.View.Pane.Room.addIgnoreIcon(roomJid, userJid);
},
/** Function: unignoreUser
* Unignore an ignored user and remove the ignore icon of the roster item.
*
* Parameters:
* (String) roomJid - Room in which the user should be unignored
* (String) userJid - User which should be unignored
*/
unignoreUser: function(roomJid, userJid) {
Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
Candy.View.Pane.Room.removeIgnoreIcon(roomJid, userJid);
},
/** Function: addIgnoreIcon
* Add the ignore icon to the roster item of the specified user
*
* Parameters:
* (String) roomJid - Room in which the roster item should be updated
* (String) userJid - User of which the roster item should be updated
*/
addIgnoreIcon: function(roomJid, userJid) {
if (Candy.View.Pane.Chat.rooms[userJid]) {
$('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
}
if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
$('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
}
},
/** Function: removeIgnoreIcon
* Remove the ignore icon to the roster item of the specified user
*
* Parameters:
* (String) roomJid - Room in which the roster item should be updated
* (String) userJid - User of which the roster item should be updated
*/
removeIgnoreIcon: function(roomJid, userJid) {
if (Candy.View.Pane.Chat.rooms[userJid]) {
$('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
}
if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
$('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
}
},
/** Function: getPane
* Get the chat room pane or a subPane of it (if subPane is specified)
*
* Parameters:
* (String) roomJid - Room in which the pane lies
* (String) subPane - Sub pane of the chat room pane if needed [optional]
*/
getPane: function(roomJid, subPane) {
if (self.Chat.rooms[roomJid]) {
if(subPane) {
if(self.Chat.rooms[roomJid]['pane-' + subPane]) {
return self.Chat.rooms[roomJid]['pane-' + subPane];
} else {
self.Chat.rooms[roomJid]['pane-' + subPane] = $('#chat-room-' + self.Chat.rooms[roomJid].id).find(subPane);
return self.Chat.rooms[roomJid]['pane-' + subPane];
}
} else {
return $('#chat-room-' + self.Chat.rooms[roomJid].id);
}
}
}
};
/** Class: Candy.View.Pane.PrivateRoom
* Private room handling
*/
self.PrivateRoom = {
/** Function: open
* Opens a new private room
*
* Parameters:
* (String) roomJid - Room jid to open
* (String) roomName - Room name
* (Boolean) switchToRoom - If true, displayed room switches automatically to this room
* (e.g. when user clicks itself on another user to open a private chat)
* (Boolean) isNoConferenceRoomJid - true if a 3rd-party client sends a direct message to this user (not via the room)
* then the username is the node and not the resource. This param addresses this case.
*
* Calls:
* - <Candy.View.Event.Room.onAdd>
*/
open: function(roomJid, roomName, switchToRoom, isNoConferenceRoomJid) {
var user = isNoConferenceRoomJid ? Candy.Core.getUser() : self.Room.getUser(Strophe.getBareJidFromJid(roomJid));
// if target user is in privacy list, don't open the private chat.
if (Candy.Core.getUser().isInPrivacyList('ignore', roomJid)) {
return false;
}
if(!self.Chat.rooms[roomJid]) {
self.Room.init(roomJid, roomName, 'chat');
}
if(switchToRoom) {
self.Room.show(roomJid);
}
self.Roster.update(roomJid, new Candy.Core.ChatUser(roomJid, roomName), 'join', user);
self.Roster.update(roomJid, user, 'join', user);
self.PrivateRoom.setStatus(roomJid, 'join');
// We can't track the presence of a user if it's not a conference jid
if(isNoConferenceRoomJid) {
self.Chat.infoMessage(roomJid, $.i18n._('presenceUnknownWarningSubject'), $.i18n._('presenceUnknownWarning'));
}
Candy.View.Event.Room.onAdd({'roomJid': roomJid, type: 'chat', 'element': self.Room.getPane(roomJid)});
},
/** Function: setStatus
* Set offline or online status for private rooms (when one of the participants leaves the room)
*
* Parameters:
* (String) roomJid - Private room jid
* (String) status - "leave"/"join"
*/
setStatus: function(roomJid, status) {
var messageForm = self.Room.getPane(roomJid, '.message-form');
if(status === 'join') {
self.Chat.getTab(roomJid).addClass('online').removeClass('offline');
messageForm.children('.field').removeAttr('disabled');
messageForm.children('.submit').removeAttr('disabled');
self.Chat.getTab(roomJid);
} else {
self.Chat.getTab(roomJid).addClass('offline').removeClass('online');
messageForm.children('.field').attr('disabled', true);
messageForm.children('.submit').attr('disabled', true);
}
}
};
/** Class Candy.View.Pane.Roster
* Handles everyhing regarding roster updates.
*/
self.Roster = {
/** Function: update
* Called by <Candy.View.Observer.Presence.update> to update the roster if needed.
* Adds/removes users from the roster list or updates informations on their items (roles, affiliations etc.)
*
* TODO: Refactoring, this method has too much LOC.
*
* Parameters:
* (String) roomJid - Room JID in which the update happens
* (Candy.Core.ChatUser) user - User on which the update happens
* (String) action - one of "join", "leave", "kick" and "ban"
* (Candy.Core.ChatUser) currentUser - Current user
*/
update: function(roomJid, user, action, currentUser) {
var roomId = self.Chat.rooms[roomJid].id,
userId = Candy.Util.jidToId(user.getJid()),
usercountDiff = -1;
// a user joined the room
if(action === 'join') {
usercountDiff = 1;
var html = Mustache.to_html(Candy.View.Template.Roster.user, {
roomId: roomId,
userId : userId,
userJid: user.getJid(),
nick: user.getNick(),
displayNick: Candy.Util.crop(user.getNick(), Candy.View.getOptions().crop.roster.nickname),
role: user.getRole(),
affiliation: user.getAffiliation(),
me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
tooltipRole: $.i18n._('tooltipRole'),
tooltipIgnored: $.i18n._('tooltipIgnored')
}),
userElem = $('#user-' + roomId + '-' + userId);
if(userElem.length < 1) {
var userInserted = false,
rosterPane = self.Room.getPane(roomJid, '.roster-pane');
// there are already users in the roster
if(rosterPane.children().length > 0) {
// insert alphabetically
var userSortCompare = user.getNick().toUpperCase();
rosterPane.children().each(function() {
var elem = $(this);
if(elem.attr('data-nick').toUpperCase() > userSortCompare) {
elem.before(html);
userInserted = true;
return false;
}
return true;
});
}
// first user in roster
if(!userInserted) {
rosterPane.append(html);
}
self.Roster.joinAnimation('user-' + roomId + '-' + userId);
// only show other users joining & don't show if there's no message in the room.
if(currentUser !== undefined && user.getNick() !== currentUser.getNick() && self.Room.getUser(roomJid)) {
// always show join message in private room, even if status messages have been disabled
if (self.Chat.rooms[roomJid].type === 'chat') {
self.Chat.onInfoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
} else {
self.Chat.infoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
}
}
// user is in room but maybe the affiliation/role has changed
} else {
usercountDiff = 0;
userElem.replaceWith(html);
$('#user-' + roomId + '-' + userId).css({opacity: 1}).show();
}
// Presence of client
if (currentUser !== undefined && currentUser.getNick() === user.getNick()) {
self.Room.setUser(roomJid, user);
// add click handler for private chat
} else {
$('#user-' + roomId + '-' + userId).click(self.Roster.userClick);
}
$('#user-' + roomId + '-' + userId + ' .context').click(function(e) {
self.Chat.Context.show(e.currentTarget, roomJid, user);
e.stopPropagation();
});
// check if current user is ignoring the user who has joined.
if (currentUser !== undefined && currentUser.isInPrivacyList('ignore', user.getJid())) {
Candy.View.Pane.Room.addIgnoreIcon(roomJid, user.getJid());
}
// a user left the room
} else if(action === 'leave') {
self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
// always show leave message in private room, even if status messages have been disabled
if (self.Chat.rooms[roomJid].type === 'chat') {
self.Chat.onInfoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
} else {
self.Chat.infoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
}
// user has been kicked
} else if(action === 'kick') {
self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenKickedFromRoom', [user.getNick()]));
// user has been banned
} else if(action === 'ban') {
self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenBannedFromRoom', [user.getNick()]));
}
// Update user count
Candy.View.Pane.Chat.rooms[roomJid].usercount += usercountDiff;
if(roomJid === Candy.View.getCurrent().roomJid) {
Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
}
Candy.View.Event.Roster.onUpdate({'roomJid' : roomJid, 'user' : user, 'action': action, 'element': $('#user-' + roomId + '-' + userId)});
},
/** Function: userClick
* Click handler for opening a private room
*/
userClick: function() {
var elem = $(this);
self.PrivateRoom.open(elem.attr('data-jid'), elem.attr('data-nick'), true);
},
/** Function: joinAnimation
* Animates specified elementId on join
*
* Parameters:
* (String) elementId - Specific element to do the animation on
*/
joinAnimation: function(elementId) {
$('#' + elementId).stop(true).slideDown('normal', function() { $(this).animate({ opacity: 1 }); });
},
/** Function: leaveAnimation
* Leave animation for specified element id and removes the DOM element on completion.
*
* Parameters:
* (String) elementId - Specific element to do the animation on
*/
leaveAnimation: function(elementId) {
$('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({ opacity: 0 }, {
complete: function() {
$(this).slideUp('normal', function() { $(this).remove(); });
}
});
}
};
/** Class: Candy.View.Pane.Message
* Message submit/show handling
*/
self.Message = {
/** Function: submit
* on submit handler for message field sends the message to the server and if it's a private chat, shows the message
* immediately because the server doesn't send back those message.
*
* Parameters:
* (Event) event - Triggered event
*/
submit: function(event) {
var roomType = Candy.View.Pane.Chat.rooms[Candy.View.getCurrent().roomJid].type,
message = $(this).children('.field').val().substring(0, Candy.View.getOptions().crop.message.body);
message = Candy.View.Event.Message.beforeSend(message);
Candy.Core.Action.Jabber.Room.Message(Candy.View.getCurrent().roomJid, message, roomType);
// Private user chat. Jabber won't notify the user who has sent the message. Just show it as the user hits the button...
if(roomType === 'chat' && message) {
self.Message.show(Candy.View.getCurrent().roomJid, self.Room.getUser(Candy.View.getCurrent().roomJid).getNick(), message);
}
// Clear input and set focus to it
$(this).children('.field').val('').focus();
event.preventDefault();
},
/** Function: show
* Show a message in the message pane
*
* Parameters:
* (String) roomJid - room in which the message has been sent to
* (String) name - Name of the user which sent the message
* (String) message - Message
* (String) timestamp - [optional] Timestamp of the message, if not present, current date.
*/
show: function(roomJid, name, message, timestamp) {
message = Candy.Util.Parser.all(message.substring(0, Candy.View.getOptions().crop.message.body));
message = Candy.View.Event.Message.beforeShow({'roomJid': roomJid, 'nick': name, 'message': message});
if(!message) {
return;
}
var html = Mustache.to_html(Candy.View.Template.Message.item, {
name: name,
displayName: Candy.Util.crop(name, Candy.View.getOptions().crop.message.nickname),
message: message,
time: Candy.Util.localizedTime(timestamp || new Date().toGMTString())
});
self.Room.appendToMessagePane(roomJid, html);
var elem = self.Room.getPane(roomJid, '.message-pane').children().last();
// click on username opens private chat
elem.find('a.name').click(function(event) {
event.preventDefault();
// Check if user is online and not myself
if(name !== self.Room.getUser(Candy.View.getCurrent().roomJid).getNick() && Candy.Core.getRoom(roomJid).getRoster().get(roomJid + '/' + name)) {
Candy.View.Pane.PrivateRoom.open(roomJid + '/' + name, name, true);
}
});
// Notify the user about a new private message
if(Candy.View.getCurrent().roomJid !== roomJid || !self.Window.hasFocus()) {
self.Chat.increaseUnreadMessages(roomJid);
if(Candy.View.Pane.Chat.rooms[roomJid].type === 'chat' && !self.Window.hasFocus()) {
self.Chat.Toolbar.playSound();
}
}
if(Candy.View.getCurrent().roomJid === roomJid) {
self.Room.scrollToBottom(roomJid);
}
Candy.View.Event.Message.onShow({'roomJid': roomJid, 'element': elem, 'nick': name, 'message': message});
}
};
return self;
}(Candy.View.Pane || {}, jQuery));
/** File: template.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Template
* Contains mustache.js templates
*/
Candy.View.Template = (function(self){
self.Window = {
/**
* Unread messages - used to extend the window title
*/
unreadmessages: '({{count}}) {{title}}'
};
self.Chat = {
pane: '<div id="chat-pane">{{> tabs}}{{> toolbar}}{{> rooms}}</div>{{> modal}}',
rooms: '<div id="chat-rooms" class="rooms"></div>',
tabs: '<ul id="chat-tabs"></ul>',
tab: '<li class="roomtype-{{roomType}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}"><a href="#" class="label">{{#privateUserChat}}@{{/privateUserChat}}{{name}}</a><a href="#" class="transition"></a><a href="#" class="close">\u00D7</a><small class="unread"></small></li>',
modal: '<div id="chat-modal"><a id="admin-message-cancel" class="close" href="#">\u00D7</a><span id="chat-modal-body"></span><img src="{{resourcesPath}}img/modal-spinner.gif" id="chat-modal-spinner" /></div><div id="chat-modal-overlay"></div>',
adminMessage: '<dt>{{time}}</dt><dd class="adminmessage"><span class="label">{{sender}}</span>{{subject}} {{message}}</dd>',
infoMessage: '<dt>{{time}}</dt><dd class="infomessage">{{subject}} {{message}}</dd>',
toolbar: '<ul id="chat-toolbar"><li id="emoticons-icon" data-tooltip="{{tooltipEmoticons}}"></li><li id="chat-sound-control" class="checked" data-tooltip="{{tooltipSound}}">{{> soundcontrol}}</li><li id="chat-autoscroll-control" class="checked" data-tooltip="{{tooltipAutoscroll}}"></li><li class="checked" id="chat-statusmessage-control" data-tooltip="{{tooltipStatusmessage}}"></li><li class="context" data-tooltip="{{tooltipAdministration}}"></li><li class="usercount" data-tooltip="{{tooltipUsercount}}"><span id="chat-usercount"></span></li></ul>',
soundcontrol: '<script type="text/javascript">var audioplayerListener = new Object(); audioplayerListener.onInit = function() { };'
+ '</script><object id="chat-sound-player" type="application/x-shockwave-flash" data="{{resourcesPath}}audioplayer.swf"'
+ ' width="0" height="0"><param name="movie" value="{{resourcesPath}}audioplayer.swf" /><param name="AllowScriptAccess"'
+ ' value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{resourcesPath}}notify.mp3" />'
+ '</object>',
Context: {
menu: '<div id="context-menu"><ul></ul></div>',
menulinks: '<li class="{{class}}" id="context-menu-{{id}}">{{label}}</li>',
contextModalForm: '<form action="#" id="context-modal-form"><label for="context-modal-label">{{_label}}</label><input type="text" name="contextModalField" id="context-modal-field" /><input type="submit" class="button" name="send" value="{{_submit}}" /></form>',
adminMessageReason: '<a id="admin-message-cancel" class="close" href="#">×</a><p>{{_action}}</p>{{#reason}}<p>{{_reason}}</p>{{/reason}}'
},
tooltip: '<div id="tooltip"><div></div></div>'
};
self.Room = {
pane: '<div class="room-pane roomtype-{{roomType}}" id="chat-room-{{roomId}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}">{{> roster}}{{> messages}}{{> form}}</div>',
subject: '<dt>{{time}}</dt><dd class="subject"><span class="label">{{roomName}}</span>{{_roomSubject}} {{subject}}</dd>',
form: '<div class="message-form-wrapper"></div><form method="post" class="message-form"><input name="message" class="field" type="text" autocomplete="off" maxlength="1000" /><input type="submit" class="submit" name="submit" value="{{_messageSubmit}}" /></form>'
};
self.Roster = {
pane: '<div class="roster-pane"></div>',
user: '<div class="user role-{{role}} affiliation-{{affiliation}}{{#me}} me{{/me}}" id="user-{{roomId}}-{{userId}}" data-jid="{{userJid}}" data-nick="{{nick}}" data-role="{{role}}" data-affiliation="{{affiliation}}"><div class="label">{{displayNick}}</div><ul><li class="context" id="context-{{roomId}}-{{userId}}"></li><li class="role role-{{role}} affiliation-{{affiliation}}" data-tooltip="{{tooltipRole}}"></li><li class="ignore" data-tooltip="{{tooltipIgnored}}"></li></ul></div>'
};
self.Message = {
pane: '<div class="message-pane-wrapper"><dl class="message-pane"></dl></div>',
item: '<dt>{{time}}</dt><dd><span class="label"><a href="#" class="name">{{displayName}}</a></span>{{{message}}}</dd>'
};
self.Login = {
form: '<form method="post" id="login-form" class="login-form">'
+ '{{#displayUsername}}<label for="username">{{_labelUsername}}</label><input type="text" id="username" name="username"/>{{/displayUsername}}'
+ '{{#presetJid}}<input type="hidden" id="username" name="username" value="{{presetJid}}"/>{{/presetJid}}'
+ '{{#displayPassword}}<label for="password">{{_labelPassword}}</label><input type="password" id="password" name="password" />{{/displayPassword}}'
+ '<input type="submit" class="button" value="{{_loginSubmit}}" /></form>'
};
self.PresenceError = {
enterPasswordForm: '<strong>{{_label}}</strong>'
+ '<form method="post" id="enter-password-form" class="enter-password-form">'
+ '<label for="password">{{_labelPassword}}</label><input type="password" id="password" name="password" />'
+ '<input type="submit" class="button" value="{{_joinSubmit}}" /></form>',
nicknameConflictForm: '<strong>{{_label}}</strong>'
+ '<form method="post" id="nickname-conflict-form" class="nickname-conflict-form">'
+ '<label for="nickname">{{_labelNickname}}</label><input type="text" id="nickname" name="nickname" />'
+ '<input type="submit" class="button" value="{{_loginSubmit}}" /></form>',
displayError: '<strong>{{_error}}</strong>'
};
return self;
}(Candy.View.Template || {}));
/** File: translation.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Translation
* Contains translations
*/
Candy.View.Translation = {
'en' : {
'status': 'Status: %s',
'statusConnecting': 'Connecting...',
'statusConnected' : 'Connected',
'statusDisconnecting': 'Disconnecting...',
'statusDisconnected' : 'Disconnected',
'statusAuthfail': 'Authentication failed',
'roomSubject' : 'Subject:',
'messageSubmit': 'Send',
'labelUsername': 'Username:',
'labelPassword': 'Password:',
'loginSubmit' : 'Login',
'loginInvalid' : 'Invalid JID',
'reason' : 'Reason:',
'subject' : 'Subject:',
'reasonWas' : 'Reason was: %s.',
'kickActionLabel' : 'Kick',
'youHaveBeenKickedBy' : 'You have been kicked from %2$s by %1$s',
'youHaveBeenKicked' : 'You have been kicked from %s',
'banActionLabel' : 'Ban',
'youHaveBeenBannedBy' : 'You have been banned from %1$s by %2$s',
'youHaveBeenBanned' : 'You have been banned from %s',
'privateActionLabel' : 'Private chat',
'ignoreActionLabel' : 'Ignore',
'unignoreActionLabel' : 'Unignore',
'setSubjectActionLabel': 'Change Subject',
'administratorMessageSubject' : 'Administrator',
'userJoinedRoom' : '%s joined the room.',
'userLeftRoom' : '%s left the room.',
'userHasBeenKickedFromRoom': '%s has been kicked from the room.',
'userHasBeenBannedFromRoom': '%s has been banned from the room.',
'presenceUnknownWarningSubject': 'Notice:',
'presenceUnknownWarning' : 'This user might be offline. We can\'t track his presence.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderator',
'tooltipIgnored' : 'You ignore this user',
'tooltipEmoticons' : 'Emoticons',
'tooltipSound' : 'Play sound for new private messages',
'tooltipAutoscroll' : 'Autoscroll',
'tooltipStatusmessage' : 'Display status messages',
'tooltipAdministration' : 'Room Administration',
'tooltipUsercount' : 'Room Occupants',
'enterRoomPassword' : 'Room "%s" is password protected.',
'enterRoomPasswordSubmit' : 'Join room',
'passwordEnteredInvalid' : 'Invalid password for room "%s".',
'nicknameConflict': 'Username already in use. Please choose another one.',
'errorMembersOnly': 'You can\'t join room "%s": Insufficient rights.',
'errorMaxOccupantsReached': 'You can\'t join room "%s": Too many occupants.',
'antiSpamMessage' : 'Please do not spam. You have been blocked for a short-time.'
},
'de' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinden...',
'statusConnected' : 'Verbunden',
'statusDisconnecting': 'Verbindung trennen...',
'statusDisconnected' : 'Verbindung getrennt',
'statusAuthfail': 'Authentifizierung fehlgeschlagen',
'roomSubject' : 'Thema:',
'messageSubmit': 'Senden',
'labelUsername': 'Benutzername:',
'labelPassword': 'Passwort:',
'loginSubmit' : 'Anmelden',
'loginInvalid' : 'Ungültige JID',
'reason' : 'Begründung:',
'subject' : 'Titel:',
'reasonWas' : 'Begründung: %s.',
'kickActionLabel' : 'Kick',
'youHaveBeenKickedBy' : 'Du wurdest soeben aus dem Raum %1$s gekickt (%2$s)',
'youHaveBeenKicked' : 'Du wurdest soeben aus dem Raum %s gekickt',
'banActionLabel' : 'Ban',
'youHaveBeenBannedBy' : 'Du wurdest soeben aus dem Raum %1$s verbannt (%2$s)',
'youHaveBeenBanned' : 'Du wurdest soeben aus dem Raum %s verbannt',
'privateActionLabel' : 'Privater Chat',
'ignoreActionLabel' : 'Ignorieren',
'unignoreActionLabel' : 'Nicht mehr ignorieren',
'setSubjectActionLabel': 'Thema ändern',
'administratorMessageSubject' : 'Administrator',
'userJoinedRoom' : '%s hat soeben den Raum betreten.',
'userLeftRoom' : '%s hat soeben den Raum verlassen.',
'userHasBeenKickedFromRoom': '%s ist aus dem Raum gekickt worden.',
'userHasBeenBannedFromRoom': '%s ist aus dem Raum verbannt worden.',
'presenceUnknownWarningSubject': 'Hinweis:',
'presenceUnknownWarning' : 'Dieser Benutzer könnte bereits abgemeldet sein. Wir können seine Anwesenheit nicht verfolgen.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderator',
'tooltipIgnored' : 'Du ignorierst diesen Benutzer',
'tooltipEmoticons' : 'Smileys',
'tooltipSound' : 'Ton abspielen bei neuen privaten Nachrichten',
'tooltipAutoscroll' : 'Autoscroll',
'tooltipStatusmessage' : 'Statusnachrichten anzeigen',
'tooltipAdministration' : 'Raum Administration',
'tooltipUsercount' : 'Anzahl Benutzer im Raum',
'enterRoomPassword' : 'Raum "%s" ist durch ein Passwort geschützt.',
'enterRoomPasswordSubmit' : 'Raum betreten',
'passwordEnteredInvalid' : 'Inkorrektes Passwort für Raum "%s".',
'nicknameConflict': 'Der Benutzername wird bereits verwendet. Bitte wähle einen anderen.',
'errorMembersOnly': 'Du kannst den Raum "%s" nicht betreten: Ungenügende Rechte.',
'errorMaxOccupantsReached': 'Du kannst den Raum "%s" nicht betreten: Benutzerlimit erreicht.',
'antiSpamMessage' : 'Bitte nicht spammen. Du wurdest für eine kurze Zeit blockiert.'
},
'fr' : {
'status': 'Status: %s',
'statusConnecting': 'Connecter...',
'statusConnected' : 'Connecté.',
'statusDisconnecting': 'Déconnecter....',
'statusDisconnected' : 'Déconnecté.',
'statusAuthfail': 'Authentification a échoué',
'roomSubject' : 'Sujet:',
'messageSubmit': 'Envoyer',
'labelUsername': 'Nom d\'utilisateur:',
'labelPassword': 'Mot de passe:',
'loginSubmit' : 'Inscription',
'loginInvalid' : 'JID invalide',
'reason' : 'Justification:',
'subject' : 'Titre:',
'reasonWas' : 'Justification: %s.',
'kickActionLabel' : 'Kick',
'youHaveBeenKickedBy' : 'Tu as été expulsé de le salon %1$s (%2$s)',
'youHaveBeenKicked' : 'Tu as été expulsé de le salon %s',
'banActionLabel' : 'Ban',
'youHaveBeenBannedBy' : 'Tu as été banni de le salon %1$s (%2$s)',
'youHaveBeenBanned' : 'Tu as été banni de le salon %s',
'privateActionLabel' : 'Chat privé',
'ignoreActionLabel' : 'Ignorer',
'unignoreActionLabel' : 'Ne plus ignorer',
'setSubjectActionLabel': 'Changer le sujet',
'administratorMessageSubject' : 'Administrateur',
'userJoinedRoom' : '%s vient d\'entrer dans le salon.',
'userLeftRoom' : '%s vient de quitter le salon.',
'userHasBeenKickedFromRoom': '%s a été expulsé du salon.',
'userHasBeenBannedFromRoom': '%s a été banni du salon.',
'presenceUnknownWarningSubject': 'Note:',
'presenceUnknownWarning' : 'Cet utilisateur n\'est malheureusement plus connecté, le message ne sera pas envoyé.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Modérateur',
'tooltipIgnored' : 'Tu ignores cette personne',
'tooltipEmoticons' : 'Smileys',
'tooltipSound' : 'Jouer un son lorsque tu reçois de nouveaux messages privés',
'tooltipAutoscroll' : 'Auto-defilement',
'tooltipStatusmessage' : 'Messages d\'état',
'tooltipAdministration' : 'Administrer le salon',
'tooltipUsercount' : 'Nombre d\'utilisateurs dans le salon',
'enterRoomPassword' : 'Le salon "%s" est protégé par un mot de passe.',
'enterRoomPasswordSubmit' : 'Entrer dans le salon',
'passwordEnteredInvalid' : 'Le mot de passe four le salon "%s" est invalide.',
'nicknameConflict': 'Le nom d\'utilisateur est déjà utilisé. Choisi un autre.',
'errorMembersOnly': 'Tu ne peut pas entrer de le salon "%s": droits insuffisants.',
'errorMaxOccupantsReached': 'Tu ne peut pas entrer de le salon "%s": Limite d\'utilisateur atteint.',
'antiSpamMessage' : 'S\'il te plaît, pas de spam. Tu as été bloqué pendant une courte période..'
},
'nl' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinding maken...',
'statusConnected' : 'Verbinding is gereed',
'statusDisconnecting': 'Verbinding verbreken...',
'statusDisconnected' : 'Verbinding is verbroken',
'statusAuthfail': 'Authenticatie is mislukt',
'roomSubject' : 'Onderwerp:',
'messageSubmit': 'Verstuur',
'labelUsername': 'Gebruikersnaam:',
'labelPassword': 'Wachtwoord:',
'loginSubmit' : 'Inloggen',
'loginInvalid' : 'JID is onjuist',
'reason' : 'Reden:',
'subject' : 'Onderwerp:',
'reasonWas' : 'De reden was: %s.',
'kickActionLabel' : 'Verwijderen',
'youHaveBeenKickedBy' : 'Je bent verwijderd van %1$s door %2$s',
'youHaveBeenKicked' : 'Je bent verwijderd van %s',
'banActionLabel' : 'Blokkeren',
'youHaveBeenBannedBy' : 'Je bent geblokkeerd van %1$s door %2$s',
'youHaveBeenBanned' : 'Je bent geblokkeerd van %s',
'privateActionLabel' : 'Prive gesprek',
'ignoreActionLabel' : 'Negeren',
'unignoreActionLabel' : 'Niet negeren',
'setSubjectActionLabel': 'Onderwerp wijzigen',
'administratorMessageSubject' : 'Beheerder',
'userJoinedRoom' : '%s komt de chat binnen.',
'userLeftRoom' : '%s heeft de chat verlaten.',
'userHasBeenKickedFromRoom': '%s is verwijderd.',
'userHasBeenBannedFromRoom': '%s is geblokkeerd.',
'presenceUnknownWarningSubject': 'Mededeling:',
'presenceUnknownWarning' : 'Deze gebruiker is waarschijnlijk offline, we kunnen zijn/haar aanwezigheid niet vaststellen.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderator',
'tooltipIgnored' : 'Je negeert deze gebruiker',
'tooltipEmoticons' : 'Emotie-iconen',
'tooltipSound' : 'Speel een geluid af bij nieuwe privé berichten.',
'tooltipAutoscroll' : 'Automatisch scrollen',
'tooltipStatusmessage' : 'Statusberichten weergeven',
'tooltipAdministration' : 'Instellingen',
'tooltipUsercount' : 'Gebruikers',
'enterRoomPassword' : 'De Chatroom "%s" is met een wachtwoord beveiligd.',
'enterRoomPasswordSubmit' : 'Ga naar Chatroom',
'passwordEnteredInvalid' : 'Het wachtwoord voor de Chatroom "%s" is onjuist.',
'nicknameConflict': 'De gebruikersnaam is reeds in gebruik. Probeer a.u.b. een andere gebruikersnaam.',
'errorMembersOnly': 'Je kunt niet deelnemen aan de Chatroom "%s": Je hebt onvoldoende rechten.',
'errorMaxOccupantsReached': 'Je kunt niet deelnemen aan de Chatroom "%s": Het maximum aantal gebruikers is bereikt.',
'antiSpamMessage' : 'Het is niet toegestaan om veel berichten naar de server te versturen. Je bent voor een korte periode geblokkeerd.'
},
'es': {
'status': 'Estado: %s',
'statusConnecting': 'Conectando...',
'statusConnected' : 'Conectado',
'statusDisconnecting': 'Desconectando...',
'statusDisconnected' : 'Desconectado',
'statusAuthfail': 'Falló la autenticación',
'roomSubject' : 'Asunto:',
'messageSubmit': 'Enviar',
'labelUsername': 'Usuario:',
'labelPassword': 'Clave:',
'loginSubmit' : 'Entrar',
'loginInvalid' : 'JID no válido',
'reason' : 'Razón:',
'subject' : 'Asunto:',
'reasonWas' : 'La razón fue: %s.',
'kickActionLabel' : 'Expulsar',
'youHaveBeenKickedBy' : 'Has sido expulsado de %1$s por %2$s',
'youHaveBeenKicked' : 'Has sido expulsado de %s',
'banActionLabel' : 'Prohibir',
'youHaveBeenBannedBy' : 'Has sido expulsado permanentemente de %1$s por %2$s',
'youHaveBeenBanned' : 'Has sido expulsado permanentemente de %s',
'privateActionLabel' : 'Chat privado',
'ignoreActionLabel' : 'Ignorar',
'unignoreActionLabel' : 'No ignorar',
'setSubjectActionLabel': 'Cambiar asunto',
'administratorMessageSubject' : 'Administrador',
'userJoinedRoom' : '%s se ha unido a la sala.',
'userLeftRoom' : '%s ha dejado la sala.',
'userHasBeenKickedFromRoom': '%s ha sido expulsado de la sala.',
'userHasBeenBannedFromRoom': '%s ha sido expulsado permanentemente de la sala.',
'presenceUnknownWarningSubject': 'Atención:',
'presenceUnknownWarning' : 'Éste usuario podría estar desconectado..',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderador',
'tooltipIgnored' : 'Ignoras a éste usuario',
'tooltipEmoticons' : 'Emoticonos',
'tooltipSound' : 'Reproducir un sonido para nuevos mensajes privados',
'tooltipAutoscroll' : 'Desplazamiento automático',
'tooltipStatusmessage' : 'Mostrar mensajes de estado',
'tooltipAdministration' : 'Administración de la sala',
'tooltipUsercount' : 'Usuarios en la sala',
'enterRoomPassword' : 'La sala "%s" está protegida mediante contraseña.',
'enterRoomPasswordSubmit' : 'Unirse a la sala',
'passwordEnteredInvalid' : 'Contraseña incorrecta para la sala "%s".',
'nicknameConflict': 'El nombre de usuario ya está siendo utilizado. Por favor elija otro.',
'errorMembersOnly': 'No se puede unir a la sala "%s": no tiene privilegios suficientes.',
'errorMaxOccupantsReached': 'No se puede unir a la sala "%s": demasiados participantes.',
'antiSpamMessage' : 'Por favor, no hagas spam. Has sido bloqueado temporalmente.'
},
'cn': {
'status': '状态: %s',
'statusConnecting': '连接中...',
'statusConnected': '已连接',
'statusDisconnecting': '断开连接中...',
'statusDisconnected': '已断开连接',
'statusAuthfail': '认证失败',
'roomSubject': '主题:',
'messageSubmit': '发送',
'labelUsername': '用户名:',
'labelPassword': '密码:',
'loginSubmit': '登录',
'loginInvalid': '用户名不合法',
'reason': '原因:',
'subject': '主题:',
'reasonWas': '原因是: %s.',
'kickActionLabel': '踢除',
'youHaveBeenKickedBy': '你在 %1$s 被管理者 %2$s 请出房间',
'banActionLabel': '禁言',
'youHaveBeenBannedBy': '你在 %1$s 被管理者 %2$s 禁言',
'privateActionLabel': '单独对话',
'ignoreActionLabel': '忽略',
'unignoreActionLabel': '不忽略',
'setSubjectActionLabel': '变更主题',
'administratorMessageSubject': '管理员',
'userJoinedRoom': '%s 加入房间',
'userLeftRoom': '%s 离开房间',
'userHasBeenKickedFromRoom': '%s 被请出这个房间',
'userHasBeenBannedFromRoom': '%s 被管理者禁言',
'presenceUnknownWarningSubject': '注意:',
'presenceUnknownWarning': '这个会员可能已经下线,不能追踪到他的连接信息',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole': '管理',
'tooltipIgnored': '你忽略了这个会员',
'tooltipEmoticons': '表情',
'tooltipSound': '新消息发音',
'tooltipAutoscroll': '滚动条',
'tooltipStatusmessage': '禁用状态消息',
'tooltipAdministration': '房间管理',
'tooltipUsercount': '房间占有者',
'enterRoomPassword': '登录房间 "%s" 需要密码.',
'enterRoomPasswordSubmit': '加入房间',
'passwordEnteredInvalid': '登录房间 "%s" 的密码不正确',
'nicknameConflict': '用户名已经存在,请另选一个',
'errorMembersOnly': '您的权限不够,不能登录房间 "%s" ',
'errorMaxOccupantsReached': '房间 "%s" 的人数已达上限,您不能登录',
'antiSpamMessage': '因为您在短时间内发送过多的消息 服务器要阻止您一小段时间。'
},
'ja' : {
'status' : 'ステータス: %s',
'statusConnecting' : '接続中…',
'statusConnected' : '接続されました',
'statusDisconnecting' : 'ディスコネクト中…',
'statusDisconnected' : 'ディスコネクトされました',
'statusAuthfail' : '認証に失敗しました',
'roomSubject' : 'トピック:',
'messageSubmit' : '送信',
'labelUsername' : 'ユーザーネーム:',
'labelPassword' : 'パスワード:',
'loginSubmit' : 'ログイン',
'loginInvalid' : 'ユーザーネームが正しくありません',
'reason' : '理由:',
'subject' : 'トピック:',
'reasonWas' : '理由: %s。',
'kickActionLabel' : 'キック',
'youHaveBeenKickedBy' : 'あなたは%2$sにより%1$sからキックされました。',
'youHaveBeenKicked' : 'あなたは%sからキックされました。',
'banActionLabel' : 'アカウントバン',
'youHaveBeenBannedBy' : 'あなたは%2$sにより%1$sからアカウントバンされました。',
'youHaveBeenBanned' : 'あなたは%sからアカウントバンされました。',
'privateActionLabel' : 'プライベートメッセージ',
'ignoreActionLabel' : '無視する',
'unignoreActionLabel' : '無視をやめる',
'setSubjectActionLabel' : 'トピックを変える',
'administratorMessageSubject' : '管理者',
'userJoinedRoom' : '%sは入室しました。',
'userLeftRoom' : '%sは退室しました。',
'userHasBeenKickedFromRoom' : '%sは部屋からキックされました。',
'userHasBeenBannedFromRoom' : '%sは部屋からアカウントバンされました。',
'presenceUnknownWarningSubject' : '忠告:',
'presenceUnknownWarning' : 'このユーザーのステータスは不明です。',
'dateFormat' : 'dd.mm.yyyy',
'timeFormat' : 'HH:MM:ss',
'tooltipRole' : 'モデレーター',
'tooltipIgnored' : 'このユーザーを無視設定にしている',
'tooltipEmoticons' : '絵文字',
'tooltipSound' : '新しいメッセージが届くたびに音を鳴らす',
'tooltipAutoscroll' : 'オートスクロール',
'tooltipStatusmessage' : 'ステータスメッセージを表示',
'tooltipAdministration' : '部屋の管理',
'tooltipUsercount' : 'この部屋の参加者の数',
'enterRoomPassword' : '"%s"の部屋に入るにはパスワードが必要です。',
'enterRoomPasswordSubmit' : '部屋に入る',
'passwordEnteredInvalid' : '"%s"のパスワードと異なるパスワードを入力しました。',
'nicknameConflict' : 'このユーザーネームはすでに利用されているため、別のユーザーネームを選んでください。',
'errorMembersOnly' : '"%s"の部屋に入ることができません: 利用権限を満たしていません。',
'errorMaxOccupantsReached' : '"%s"の部屋に入ることができません: 参加者の数はすでに上限に達しました。',
'antiSpamMessage' : 'スパムなどの行為はやめてください。あなたは一時的にブロックされました。'
}
};
\ No newline at end of file
<html>
<head>
<title>Fastpath</title>
<script src="js/jquery.min.js"></script>
<script src="js/md5.js"></script>
<script src="js/base64.js"></script>
<script src="js/strophe.js"></script>
<script src="js/strophe-openfire.js"></script>
<script src="js/fastpath.js"></script>
<link rel="shortcut icon" href="favicon.ico"/>
<link rel="stylesheet" href="css/fastpath.css">
</head>
<body>
<button onclick="joinWorkgroup('flowers', {username: 'John Doe', email: 'john@large', question: 'what is the meaning of life'})" id="fastpath">Offline</button>
</body>
</html>
// 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;
})();
var connection = null;
$(document).ready(function ()
{
connection = new Openfire.Connection(window.location.protocol + "//" + window.location.host + '/http-bind/');
connection.resource = Math.random().toString(36).substr(2, 20);
connection.rawInput = function (data) { console.log('RECV: ' + data); };
connection.rawOutput = function (data) { console.log('SEND: ' + data); };
connection.connect(window.location.hostname, null, function (status)
{
if (status == Strophe.Status.CONNECTED)
{
console.log('connected');
connection.send($pres());
connection.addHandler(onMessage, 'http://jabber.org/protocol/workgroup', 'message');
$('#fastpath').html('Online');
} else {
console.log('status', status);
}
});
});
function joinWorkgroup(workgroup, form)
{
if (connection != null && connection.connected)
{
var iq = $iq({to: workgroup + "@workgroup." + connection.domain, type: 'set'}).c('join-queue', {xmlns: 'http://jabber.org/protocol/workgroup'});
iq.c('queue-notifications').up();
iq.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
var items = Object.getOwnPropertyNames(form)
for (var i=0; i< items.length; i++)
{
iq.c('field', {var: items[i]}).c('value').t(form[items[i]]).up().up();
}
iq.up();
connection.sendIQ(iq,
function (res) {
console.log('join workgroup ok', res);
},
function (err) {
console.log('join workgroup error', err);
}
);
}
}
function onMessage(message)
{
console.log('onMessage', message);
$(message).find('x').each(function()
{
var xmlns = $(this).attr("xmlns");
if (xmlns == "jabber:x:conference")
{
var roomJid = $(this).attr("jid");
window.location.href = "/ofmeet/?r=" + Strophe.getNodeFromJid(roomJid);
}
});
return true;
}
/*! jQuery v2.0.3 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
*/
(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.3",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=st(),k=st(),N=st(),E=!1,S=function(e,t){return e===t?(E=!0,0):0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],q=L.pop,H=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){H.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+mt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,r,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function at(e){return e[v]=!0,e}function ut(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function lt(e,t){var n=e.split("|"),r=e.length;while(r--)i.attrHandle[n[r]]=t}function ct(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return at(function(t){return t=+t,at(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.defaultView;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.attachEvent&&r!==r.top&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ut(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=ut(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=Q.test(t.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ut(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=Q.test(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=Q.test(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return ct(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?ct(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:at,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?at(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:at(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?at(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:at(function(e){return function(t){return ot(e,t).length>0}}),contains:at(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:at(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},i.pseudos.nth=i.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=ft(t);function dt(){}dt.prototype=i.filters=i.pseudos,i.setFilters=new dt;function gt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[v]&&(r=bt(r)),i&&!i[v]&&(i=bt(i,o)),at(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function wt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=yt(function(e){return e===t},a,!0),p=yt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[yt(vt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return bt(l>1&&vt(f),l>1&&mt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&wt(e.slice(l,r)),o>r&&wt(e=e.slice(r)),o>r&&mt(e))}f.push(n)}return vt(f)}function Tt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=q.call(f));y=xt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?at(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function kt(e,t,r,o){var s,u,l,c,p,f=gt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&mt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}n.sortStable=v.split("").sort(S).join("")===v,n.detectDuplicates=E,c(),n.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(p.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||lt("type|href|height|width",function(e,t,n){return n?undefined:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||lt("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?undefined:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||lt(R,function(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}),x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!a||n&&!u||(t=t||[],t=[e,t.slice?t.slice():t],r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){var r;return t===undefined||t&&"string"==typeof t&&n===undefined?(r=this.get(e,t),r!==undefined?r:this.get(e,x.camelCase(t))):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)
};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,i=0,o=x(this),s=e.match(w)||[];while(t=s[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},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(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|&#?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ct={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1></$2>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1></$2>")+a[2],l=a[0];while(l--)o=o.lastChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[q.expando],o&&(t=q.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);q.cache[o]&&delete q.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=q.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function qt(t){return e.getComputedStyle(t,null)}function Ht(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=q.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=qt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return Ht(this,!0)},hide:function(){return Ht(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Lt(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||qt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=qt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(xt[0].contentWindow||xt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=Mt(e,t),xt.detach()),Nt[e]=n),n}function Mt(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,t){x.cssHooks[t]={get:function(e,n,r){return n?0===e.offsetWidth&&bt.test(x.css(e,"display"))?x.swap(e,Et,function(){return Pt(e,t,r)}):Pt(e,t,r):undefined},set:function(e,n,r){var i=r&&qt(e);return Ot(e,n,r?Ft(e,t,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,t){return t?x.swap(e,{display:"inline-block"},vt,[e,"marginRight"]):undefined}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,t){x.cssHooks[t]={get:function(e,n){return n?(n=vt(e,t),Ct.test(n)?x(e).position()[t]+"px":n):undefined}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+jt[r]+t]=o[r]||o[r-2]||o[0];return i}},wt.test(e)||(x.cssHooks[e+t].set=Ot)});var Wt=/%20/g,$t=/\[\]$/,Bt=/\r?\n/g,It=/^(?:submit|button|image|reset|file)$/i,zt=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&zt.test(this.nodeName)&&!It.test(e)&&(this.checked||!ot.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(Bt,"\r\n")}}):{name:t.name,value:n.replace(Bt,"\r\n")}}).get()}}),x.param=function(e,t){var n,r=[],i=function(e,t){t=x.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(t===undefined&&(t=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){i(this.name,this.value)});else for(n in e)_t(n,e[n],t,i);return r.join("&").replace(Wt,"+")};function _t(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||$t.test(e)?r(e,i):_t(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)_t(e+"["+i+"]",t[i],n,r)}x.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(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)
},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var Xt,Ut,Yt=x.now(),Vt=/\?/,Gt=/#.*$/,Jt=/([?&])_=[^&]*/,Qt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Kt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Zt=/^(?:GET|HEAD)$/,en=/^\/\//,tn=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,nn=x.fn.load,rn={},on={},sn="*/".concat("*");try{Ut=i.href}catch(an){Ut=o.createElement("a"),Ut.href="",Ut=Ut.href}Xt=tn.exec(Ut.toLowerCase())||[];function un(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(w)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function ln(e,t,n,r){var i={},o=e===on;function s(a){var u;return i[a]=!0,x.each(e[a]||[],function(e,a){var l=a(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):undefined:(t.dataTypes.unshift(l),s(l),!1)}),u}return s(t.dataTypes[0])||!i["*"]&&s("*")}function cn(e,t){var n,r,i=x.ajaxSettings.flatOptions||{};for(n in t)t[n]!==undefined&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,t,n){if("string"!=typeof e&&nn)return nn.apply(this,arguments);var r,i,o,s=this,a=e.indexOf(" ");return a>=0&&(r=e.slice(a),e=e.slice(0,a)),x.isFunction(t)?(n=t,t=undefined):t&&"object"==typeof t&&(i="POST"),s.length>0&&x.ajax({url:e,type:i,dataType:"html",data:t}).done(function(e){o=arguments,s.html(r?x("<div>").append(x.parseHTML(e)).find(r):e)}).complete(n&&function(e,t){s.each(n,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ut,type:"GET",isLocal:Kt.test(Xt[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":sn,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"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?cn(cn(e,x.ajaxSettings),t):cn(x.ajaxSettings,e)},ajaxPrefilter:un(rn),ajaxTransport:un(on),ajax:function(e,t){"object"==typeof e&&(t=e,e=undefined),t=t||{};var n,r,i,o,s,a,u,l,c=x.ajaxSetup({},t),p=c.context||c,f=c.context&&(p.nodeType||p.jquery)?x(p):x.event,h=x.Deferred(),d=x.Callbacks("once memory"),g=c.statusCode||{},m={},y={},v=0,b="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===v){if(!o){o={};while(t=Qt.exec(i))o[t[1].toLowerCase()]=t[2]}t=o[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===v?i:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return v||(e=y[n]=y[n]||e,m[e]=t),this},overrideMimeType:function(e){return v||(c.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>v)for(t in e)g[t]=[g[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||b;return n&&n.abort(t),k(0,t),this}};if(h.promise(T).complete=d.add,T.success=T.done,T.error=T.fail,c.url=((e||c.url||Ut)+"").replace(Gt,"").replace(en,Xt[1]+"//"),c.type=t.method||t.type||c.method||c.type,c.dataTypes=x.trim(c.dataType||"*").toLowerCase().match(w)||[""],null==c.crossDomain&&(a=tn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===Xt[1]&&a[2]===Xt[2]&&(a[3]||("http:"===a[1]?"80":"443"))===(Xt[3]||("http:"===Xt[1]?"80":"443")))),c.data&&c.processData&&"string"!=typeof c.data&&(c.data=x.param(c.data,c.traditional)),ln(rn,c,t,T),2===v)return T;u=c.global,u&&0===x.active++&&x.event.trigger("ajaxStart"),c.type=c.type.toUpperCase(),c.hasContent=!Zt.test(c.type),r=c.url,c.hasContent||(c.data&&(r=c.url+=(Vt.test(r)?"&":"?")+c.data,delete c.data),c.cache===!1&&(c.url=Jt.test(r)?r.replace(Jt,"$1_="+Yt++):r+(Vt.test(r)?"&":"?")+"_="+Yt++)),c.ifModified&&(x.lastModified[r]&&T.setRequestHeader("If-Modified-Since",x.lastModified[r]),x.etag[r]&&T.setRequestHeader("If-None-Match",x.etag[r])),(c.data&&c.hasContent&&c.contentType!==!1||t.contentType)&&T.setRequestHeader("Content-Type",c.contentType),T.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+("*"!==c.dataTypes[0]?", "+sn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)T.setRequestHeader(l,c.headers[l]);if(c.beforeSend&&(c.beforeSend.call(p,T,c)===!1||2===v))return T.abort();b="abort";for(l in{success:1,error:1,complete:1})T[l](c[l]);if(n=ln(on,c,t,T)){T.readyState=1,u&&f.trigger("ajaxSend",[T,c]),c.async&&c.timeout>0&&(s=setTimeout(function(){T.abort("timeout")},c.timeout));try{v=1,n.send(m,k)}catch(C){if(!(2>v))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,t,o,a){var l,m,y,b,w,C=t;2!==v&&(v=2,s&&clearTimeout(s),n=undefined,i=a||"",T.readyState=e>0?4:0,l=e>=200&&300>e||304===e,o&&(b=pn(c,T,o)),b=fn(c,b,T,l),l?(c.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(x.lastModified[r]=w),w=T.getResponseHeader("etag"),w&&(x.etag[r]=w)),204===e||"HEAD"===c.type?C="nocontent":304===e?C="notmodified":(C=b.state,m=b.data,y=b.error,l=!y)):(y=C,(e||!C)&&(C="error",0>e&&(e=0))),T.status=e,T.statusText=(t||C)+"",l?h.resolveWith(p,[m,C,T]):h.rejectWith(p,[T,C,y]),T.statusCode(g),g=undefined,u&&f.trigger(l?"ajaxSuccess":"ajaxError",[T,c,l?m:y]),d.fireWith(p,[T,C]),u&&(f.trigger("ajaxComplete",[T,c]),--x.active||x.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,t){return x.get(e,undefined,t,"script")}}),x.each(["get","post"],function(e,t){x[t]=function(e,n,r,i){return x.isFunction(n)&&(i=i||r,r=n,n=undefined),x.ajax({url:e,type:t,dataType:i,data:n,success:r})}});function pn(e,t,n){var r,i,o,s,a=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),r===undefined&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in a)if(a[i]&&a[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}s||(s=i)}o=o||s}return o?(o!==u[0]&&u.unshift(o),n[o]):undefined}function fn(e,t,n,r){var i,o,s,a,u,l={},c=e.dataTypes.slice();if(c[1])for(s in e.converters)l[s.toLowerCase()]=e.converters[s];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(s=l[u+" "+o]||l["* "+o],!s)for(i in l)if(a=i.split(" "),a[1]===o&&(s=l[u+" "+a[0]]||l["* "+a[0]])){s===!0?s=l[i]:l[i]!==!0&&(o=a[0],c.unshift(a[1]));break}if(s!==!0)if(s&&e["throws"])t=s(t);else try{t=s(t)}catch(p){return{state:"parsererror",error:s?p:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===undefined&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),x.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,i){t=x("<script>").prop({async:!0,charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&i("error"===e.type?404:200,e.type)}),o.head.appendChild(t[0])},abort:function(){n&&n()}}}});var hn=[],dn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=hn.pop()||x.expando+"_"+Yt++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,s,a=t.jsonp!==!1&&(dn.test(t.url)?"url":"string"==typeof t.data&&!(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&dn.test(t.data)&&"data");return a||"jsonp"===t.dataTypes[0]?(i=t.jsonpCallback=x.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,a?t[a]=t[a].replace(dn,"$1"+i):t.jsonp!==!1&&(t.url+=(Vt.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return s||x.error(i+" was not called"),s[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){s=arguments},r.always(function(){e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,hn.push(i)),s&&x.isFunction(o)&&o(s[0]),s=o=undefined}),"script"):undefined}),x.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(e){}};var gn=x.ajaxSettings.xhr(),mn={0:200,1223:204},yn=0,vn={};e.ActiveXObject&&x(e).on("unload",function(){for(var e in vn)vn[e]();vn=undefined}),x.support.cors=!!gn&&"withCredentials"in gn,x.support.ajax=gn=!!gn,x.ajaxTransport(function(e){var t;return x.support.cors||gn&&!e.crossDomain?{send:function(n,r){var i,o,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(i in e.xhrFields)s[i]=e.xhrFields[i];e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(i in n)s.setRequestHeader(i,n[i]);t=function(e){return function(){t&&(delete vn[o],t=s.onload=s.onerror=null,"abort"===e?s.abort():"error"===e?r(s.status||404,s.statusText):r(mn[s.status]||s.status,s.statusText,"string"==typeof s.responseText?{text:s.responseText}:undefined,s.getAllResponseHeaders()))}},s.onload=t(),s.onerror=t("error"),t=vn[o=yn++]=t("abort"),s.send(e.hasContent&&e.data||null)},abort:function(){t&&t()}}:undefined});var xn,bn,wn=/^(?:toggle|show|hide)$/,Tn=RegExp("^(?:([+-])=|)("+b+")([a-z%]*)$","i"),Cn=/queueHooks$/,kn=[An],Nn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Tn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),s=(x.cssNumber[e]||"px"!==o&&+r)&&Tn.exec(x.css(n.elem,e)),a=1,u=20;if(s&&s[3]!==o){o=o||s[3],i=i||[],s=+r||1;do a=a||".5",s/=a,x.style(n.elem,e,s+o);while(a!==(a=n.cur()/r)&&1!==a&&--u)}return i&&(s=n.start=+s||+r||0,n.unit=o,n.end=i[1]?s+(i[1]+1)*i[2]:+i[2]),n}]};function En(){return setTimeout(function(){xn=undefined}),xn=x.now()}function Sn(e,t,n){var r,i=(Nn[t]||[]).concat(Nn["*"]),o=0,s=i.length;for(;s>o;o++)if(r=i[o].call(n,t,e))return r}function jn(e,t,n){var r,i,o=0,s=kn.length,a=x.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=xn||En(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,s=0,u=l.tweens.length;for(;u>s;s++)l.tweens[s].run(o);return a.notifyWith(e,[l,o,n]),1>o&&u?n:(a.resolveWith(e,[l]),!1)},l=a.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:xn||En(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?a.resolveWith(e,[l,t]):a.rejectWith(e,[l,t]),this}}),c=l.props;for(Dn(c,l.opts.specialEasing);s>o;o++)if(r=kn[o].call(l,e,c,l.opts))return r;return x.map(c,Sn,l),x.isFunction(l.opts.start)&&l.opts.start.call(e,l),x.fx.timer(x.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function Dn(e,t){var n,r,i,o,s;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),s=x.cssHooks[r],s&&"expand"in s){o=s.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(jn,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Nn[n]=Nn[n]||[],Nn[n].unshift(t)},prefilter:function(e,t){t?kn.unshift(e):kn.push(e)}});function An(e,t,n){var r,i,o,s,a,u,l=this,c={},p=e.style,f=e.nodeType&&Lt(e),h=q.get(e,"fxshow");n.queue||(a=x._queueHooks(e,"fx"),null==a.unqueued&&(a.unqueued=0,u=a.empty.fire,a.empty.fire=function(){a.unqueued||u()}),a.unqueued++,l.always(function(){l.always(function(){a.unqueued--,x.queue(e,"fx").length||a.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(p.display="inline-block")),n.overflow&&(p.overflow="hidden",l.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],wn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show")){if("show"!==i||!h||h[r]===undefined)continue;f=!0}c[r]=h&&h[r]||x.style(e,r)}if(!x.isEmptyObject(c)){h?"hidden"in h&&(f=h.hidden):h=q.access(e,"fxshow",{}),o&&(h.hidden=!f),f?x(e).show():l.done(function(){x(e).hide()}),l.done(function(){var t;q.remove(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)s=Sn(f?h[r]:0,r,l),r in h||(h[r]=s.start,f&&(s.end=s.start,s.start="width"===r||"height"===r?1:0))}}function Ln(e,t,n,r,i){return new Ln.prototype.init(e,t,n,r,i)}x.Tween=Ln,Ln.prototype={constructor:Ln,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=Ln.propHooks[this.prop];return e&&e.get?e.get(this):Ln.propHooks._default.get(this)},run:function(e){var t,n=Ln.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Ln.propHooks._default.set(this),this}},Ln.prototype.init.prototype=Ln.prototype,Ln.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Ln.propHooks.scrollTop=Ln.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(qn(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Lt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),s=function(){var t=jn(this,x.extend({},e),o);(i||q.get(this,"finish"))&&t.stop(!0)};return s.finish=s,i||o.queue===!1?this.each(s):this.queue(o.queue,s)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return"string"!=typeof e&&(n=t,t=e,e=undefined),t&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,i=null!=e&&e+"queueHooks",o=x.timers,s=q.get(this);if(i)s[i]&&s[i].stop&&r(s[i]);else for(i in s)s[i]&&s[i].stop&&Cn.test(i)&&r(s[i]);for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));(t||!n)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=q.get(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,s=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;s>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function qn(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=jt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:qn("show"),slideUp:qn("hide"),slideToggle:qn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=Ln.prototype.init,x.fx.tick=function(){var e,t=x.timers,n=0;for(xn=x.now();t.length>n;n++)e=t[n],e()||t[n]!==e||t.splice(n--,1);t.length||x.fx.stop(),xn=undefined},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){bn||(bn=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(bn),bn=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===undefined?this:this.each(function(t){x.offset.setOffset(this,e,t)});var t,n,i=this[0],o={top:0,left:0},s=i&&i.ownerDocument;if(s)return t=s.documentElement,x.contains(t,i)?(typeof i.getBoundingClientRect!==r&&(o=i.getBoundingClientRect()),n=Hn(s),{top:o.top+n.pageYOffset-t.clientTop,left:o.left+n.pageXOffset-t.clientLeft}):o},x.offset={setOffset:function(e,t,n){var r,i,o,s,a,u,l,c=x.css(e,"position"),p=x(e),f={};"static"===c&&(e.style.position="relative"),a=p.offset(),o=x.css(e,"top"),u=x.css(e,"left"),l=("absolute"===c||"fixed"===c)&&(o+u).indexOf("auto")>-1,l?(r=p.position(),s=r.top,i=r.left):(s=parseFloat(o)||0,i=parseFloat(u)||0),x.isFunction(t)&&(t=t.call(e,n,a)),null!=t.top&&(f.top=t.top-a.top+s),null!=t.left&&(f.left=t.left-a.left+i),"using"in t?t.using.call(e,f):p.css(f)}},x.fn.extend({position:function(){if(this[0]){var e,t,n=this[0],r={top:0,left:0};return"fixed"===x.css(n,"position")?t=n.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(r=e.offset()),r.top+=x.css(e[0],"borderTopWidth",!0),r.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-r.top-x.css(n,"marginTop",!0),left:t.left-r.left-x.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,n){var r="pageYOffset"===n;x.fn[t]=function(i){return x.access(this,function(t,i,o){var s=Hn(t);return o===undefined?s?s[n]:t[i]:(s?s.scrollTo(r?e.pageXOffset:o,r?o:e.pageYOffset):t[i]=o,undefined)},t,i,arguments.length,null)}});function Hn(e){return x.isWindow(e)?e:9===e.nodeType&&e.defaultView}x.each({Height:"height",Width:"width"},function(e,t){x.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){x.fn[r]=function(r,i){var o=arguments.length&&(n||"boolean"!=typeof r),s=n||(r===!0||i===!0?"margin":"border");return x.access(this,function(t,n,r){var i;return x.isWindow(t)?t.document.documentElement["client"+e]:9===t.nodeType?(i=t.documentElement,Math.max(t.body["scroll"+e],i["scroll"+e],t.body["offset"+e],i["offset"+e],i["client"+e])):r===undefined?x.css(t,n,s):x.style(t,n,r,s)},t,o?r:undefined,o,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}),"object"==typeof e&&"object"==typeof e.document&&(e.jQuery=e.$=x)})(window);
\ No newline at end of file
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
var MD5 = (function () {
/*
* 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 */
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
var safe_add = function (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.
*/
var bit_rol = function (num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
};
/*
* Convert a string to an array of little-endian words
* If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
var str2binl = function (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) << (i%32);
}
return bin;
};
/*
* Convert an array of little-endian words to a string
*/
var binl2str = function (bin) {
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
{
str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
}
return str;
};
/*
* Convert an array of little-endian words to a hex string.
*/
var binl2hex = function (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] >> ((i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
}
return str;
};
/*
* Convert an array of little-endian words to a base-64 string
*/
var binl2b64 = function (binarray) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
var triplet, j;
for(var i = 0; i < binarray.length * 4; i += 3)
{
triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) |
(((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) |
((binarray[i+2 >> 2] >> 8 * ((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;
};
/*
* These functions implement the four basic operations the algorithm uses.
*/
var md5_cmn = function (q, a, b, x, s, t) {
return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b);
};
var md5_ff = function (a, b, c, d, x, s, t) {
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
};
var md5_gg = function (a, b, c, d, x, s, t) {
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
};
var md5_hh = function (a, b, c, d, x, s, t) {
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
};
var md5_ii = function (a, b, c, d, x, s, t) {
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
};
/*
* Calculate the MD5 of an array of little-endian words, and a bit length
*/
var core_md5 = function (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;
var olda, oldb, oldc, oldd;
for (var i = 0; i < x.length; i += 16)
{
olda = a;
oldb = b;
oldc = c;
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 [a, b, c, d];
};
/*
* Calculate the HMAC-MD5, of a key and some data
*/
var core_hmac_md5 = function (key, data) {
var bkey = str2binl(key);
if(bkey.length > 16) { bkey = core_md5(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_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
return core_md5(opad.concat(hash), 512 + 128);
};
var obj = {
/*
* These are the functions you'll usually want to call.
* They take string arguments and return either hex or base-64 encoded
* strings.
*/
hexdigest: function (s) {
return binl2hex(core_md5(str2binl(s), s.length * chrsz));
},
b64digest: function (s) {
return binl2b64(core_md5(str2binl(s), s.length * chrsz));
},
hash: function (s) {
return binl2str(core_md5(str2binl(s), s.length * chrsz));
},
hmac_hexdigest: function (key, data) {
return binl2hex(core_hmac_md5(key, data));
},
hmac_b64digest: function (key, data) {
return binl2b64(core_hmac_md5(key, data));
},
hmac_hash: function (key, data) {
return binl2str(core_hmac_md5(key, data));
},
/*
* Perform a simple self-test to see if the VM is working
*/
test: function () {
return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72";
}
};
return obj;
})();
var Openfire = {};
/** Class: Openfire.Connection
* WebSockets Connection Manager for Openfire
*
* Thie class manages an WebSockets connection
* to an Openfire XMPP server through the WebSockets plugin and dispatches events to the user callbacks as
* data arrives. It uses the server side Openfire authentication
*
* After creating a Openfire object, the user will typically
* call connect() with a user supplied callback to handle connection level
* events like authentication failure, disconnection, or connection
* complete.
*
* To send data to the connection, use send(doc) or sendRaw(text)
*
* Use xmlInput(doc) and RawInput(text) overrideable function to receive XML data coming into the
* connection.
*
* The user will also have several event handlers defined by using
* addHandler() and addTimedHandler(). These will allow the user code to
* respond to interesting stanzas or do something periodically with the
* connection. These handlers will be active once authentication is
* finished.
*
* Create and initialize a Openfire object.
*
*
* Returns:
* A new Openfire object.
*/
Openfire.Connection = function(url)
{
if (!window.WebSocket)
{
window.WebSocket=window.MozWebSocket;
if (!window.WebSocket)
{
var msg = "WebSocket not supported by this browser";
alert(msg);
throw Error(msg);
}
}
this.host = url.indexOf("/") < 0 ? url : url.split("/")[2];
this.protocol = url.indexOf("/") < 0 ? "wss:" : (url.split("/")[0] == "http:") ? "ws:" : "wss:";
this.jid = "";
this.resource = "ofmeet";
this.streamId = null;
// handler lists
this.timedHandlers = [];
this.handlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
this._idleTimeout = null;
this.authenticated = false;
this.disconnecting = false;
this.connected = false;
this.errors = 0;
this._uniqueId = Math.round(Math.random() * 10000);
// setup onIdle callback every 1/10th of a second
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
// initialize plugins
for (var k in Strophe._connectionPlugins)
{
if (Strophe._connectionPlugins.hasOwnProperty(k)) {
var ptype = Strophe._connectionPlugins[k];
// jslint complaints about the below line, but this is fine
var F = function () {};
F.prototype = ptype;
this[k] = new F();
this[k].init(this);
}
}
}
Openfire.Connection.prototype = {
/** Function: reset
* Reset the connection.
*
* This function should be called after a connection is disconnected
* before that connection is reused.
*/
reset: function ()
{
this.streamId = null;
this.timedHandlers = [];
this.handlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
this.authenticated = false;
this.disconnecting = false;
this.connected = false;
this.errors = 0;
},
/** Function: pause
* UNUSED with websockets
*/
pause: function ()
{
return;
},
/** Function: resume
* UNUSED with websockets
*/
resume: function ()
{
return;
},
/** Function: getUniqueId
* Generate a unique ID for use in <iq/> elements.
*
* All <iq/> stanzas are required to have unique id attributes. This
* function makes creating these easy. Each connection instance has
* a counter which starts from zero, and the value of this counter
* plus a colon followed by the suffix becomes the unique id. If no
* suffix is supplied, the counter is used as the unique id.
*
* Suffixes are used to make debugging easier when reading the stream
* data, and their use is recommended. The counter resets to 0 for
* every new connection for the same reason. For connections to the
* same server that authenticate the same way, all the ids should be
* the same, which makes it easy to see changes. This is useful for
* automated testing as well.
*
* Parameters:
* (String) suffix - A optional suffix to append to the id.
*
* Returns:
* A unique string to be used for the id attribute.
*/
getUniqueId: function (suffix)
{
if (typeof(suffix) == "string" || typeof(suffix) == "number") {
return ++this._uniqueId + ":" + suffix;
} else {
return ++this._uniqueId + "";
}
},
/** Function: connect
* Starts the connection process.
*
*
* Parameters:
* (String) username - The Openfire username.
* (String) pass - The user's password.
* (String) resource - The user resource for this connection.
* (Function) callback The connect callback function.
*/
connect: function (jid, pass, callback, wait, hold, route)
{
this.jid = jid.indexOf("/") > -1 ? jid : jid + '/' + this.resource;
this.username = jid.indexOf("@") < 0 ? null : jid.split("@")[0];
this.pass = pass == "" ? null : pass;
this.connect_callback = callback;
this.disconnecting = false;
this.connected = false;
this.authenticated = false;
this.errors = 0;
this._changeConnectStatus(Strophe.Status.CONNECTING, null);
this.url = this.protocol + "//" + this.host + "/ws/server?username=" + this.username + "&password=" + this.pass + "&resource=" + this.resource;
this._ws = new WebSocket(this.url, "xmpp");
this._ws.onopen = this._onopen.bind(this);
this._ws.onmessage = this._onmessage.bind(this);
this._ws.onclose = this._onclose.bind(this);
window.openfireWebSocket = this;
this.jid = this.jid.indexOf("@") < 0 ? this.resource + "@" + this.jid : this.jid;
this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
},
/**
*
* Private Function: _onopen websocket event handler
*
*/
_onopen: function()
{
this.connected = true;
this.authenticated = true;
this.resource = Strophe.getResourceFromJid(this.jid);
this.domain = Strophe.getDomainFromJid(this.jid);
try {
this._changeConnectStatus(Strophe.Status.CONNECTED, null);
} catch (e) {
throw Error("User connection callback caused an exception: " + e);
}
this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );
},
/** Function: attach
* UNUSED, use connect again
*/
attach: function()
{
return
},
/** Function: xmlInput
* User overrideable function that receives XML data coming into the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.xmlInput = function (elem) {
* > (user code)
* > };
*
* Parameters:
* (XMLElement) elem - The XML data received by the connection.
*/
xmlInput: function (elem)
{
return;
},
/** Function: xmlOutput
* User overrideable function that receives XML data sent to the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.xmlOutput = function (elem) {
* > (user code)
* > };
*
* Parameters:
* (XMLElement) elem - The XMLdata sent by the connection.
*/
xmlOutput: function (elem)
{
return;
},
/** Function: rawInput
* User overrideable function that receives raw data coming into the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.rawInput = function (data) {
* > (user code)
* > };
*
* Parameters:
* (String) data - The data received by the connection.
*/
rawInput: function (data)
{
return;
},
/** Function: rawOutput
* User overrideable function that receives raw data sent to the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.rawOutput = function (data) {
* > (user code)
* > };
*
* Parameters:
* (String) data - The data sent by the connection.
*/
rawOutput: function (data)
{
return;
},
/** Function: sendRaw
* Send a stanza in raw XML text.
*
* This function is called to push data onto the send queue to
* go out over the wire. Whenever a request is sent to the BOSH
* server, all pending data is sent and the queue is flushed.
*
* Parameters:
* xml - The stanza text XML to send.
*/
sendRaw: function(xml) {
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
if (xml != " ")
{
this.xmlOutput(this._textToXML(xml));
this.rawOutput(xml);
}
this._ws.send(xml);
},
/** Function: send
* Send a stanza.
*
* This function is called to push data onto the send queue to
* go out over the wire. Whenever a request is sent to the BOSH
* server, all pending data is sent and the queue is flushed.
*
* Parameters:
* (XMLElement |
* [XMLElement] |
* Strophe.Builder) elem - The stanza to send.
*/
send: function(elem)
{
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
var toSend = "";
if (elem === null) { return ; }
if (typeof(elem.sort) === "function")
{
for (var i = 0; i < elem.length; i++)
{
toSend += Strophe.serialize(elem[i]);
this.xmlOutput(elem[i]);
}
} else if (typeof(elem.tree) === "function") {
toSend = Strophe.serialize(elem.tree());
this.xmlOutput(elem.tree());
} else {
toSend = Strophe.serialize(elem);
this.xmlOutput(elem);
}
this.rawOutput(toSend);
this._ws.send(toSend);
},
/** Function: flush
* UNUSED
*/
flush: function ()
{
return
},
/** Function: sendIQ
* Helper function to send IQ stanzas.
*
* Parameters:
* (XMLElement) elem - The stanza to send.
* (Function) callback - The callback function for a successful request.
* (Function) errback - The callback function for a failed or timed
* out request. On timeout, the stanza will be null.
* (Integer) timeout - The time specified in milliseconds for a
* timeout to occur.
*
* Returns:
* The id used to send the IQ.
*/
sendIQ: function(elem, callback, errback, timeout) {
var timeoutHandler = null;
var that = this;
if (typeof(elem.tree) === "function") {
elem = elem.tree();
}
var id = elem.getAttribute('id');
// inject id if not found
if (!id) {
id = this.getUniqueId("sendIQ");
elem.setAttribute("id", id);
}
var handler = this.addHandler(function (stanza) {
// remove timeout handler if there is one
if (timeoutHandler) {
that.deleteTimedHandler(timeoutHandler);
}
var iqtype = stanza.getAttribute('type');
if (iqtype == 'result')
{
if (callback) {
callback(stanza);
}
} else if (iqtype == 'error') {
if (errback) {
errback(stanza);
}
} else {
throw {
name: "StropheError",
message: "Got bad IQ type of " + iqtype
};
}
}, null, 'iq', null, id);
// if timeout specified, setup timeout handler.
if (timeout)
{
timeoutHandler = this.addTimedHandler(timeout, function () {
// get rid of normal handler
that.deleteHandler(handler);
// call errback on timeout with null stanza
if (errback) {
errback(null);
}
return false;
});
}
this.send(elem);
return id;
},
/** Function: addTimedHandler
* Add a timed handler to the connection.
*
* This function adds a timed handler. The provided handler will
* be called every period milliseconds until it returns false,
* the connection is terminated, or the handler is removed. Handlers
* that wish to continue being invoked should return true.
*
* Because of method binding it is necessary to save the result of
* this function if you wish to remove a handler with
* deleteTimedHandler().
*
* Note that user handlers are not active until authentication is
* successful.
*
* Parameters:
* (Integer) period - The period of the handler.
* (Function) handler - The callback function.
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
addTimedHandler: function (period, handler)
{
var thand = new Strophe.TimedHandler(period, handler);
this.addTimeds.push(thand);
return thand;
},
/** Function: deleteTimedHandler
* Delete a timed handler for a connection.
*
* This function removes a timed handler from the connection. The
* handRef parameter is *not* the function passed to addTimedHandler(),
* but is the reference returned from addTimedHandler().
*
* Parameters:
* (Strophe.TimedHandler) handRef - The handler reference.
*/
deleteTimedHandler: function (handRef)
{
// this must be done in the Idle loop so that we don't change
// the handlers during iteration
this.removeTimeds.push(handRef);
},
/** Function: addHandler
* Add a stanza handler for the connection.
*
* This function adds a stanza handler to the connection. The
* handler callback will be called for any stanza that matches
* the parameters. Note that if multiple parameters are supplied,
* they must all match for the handler to be invoked.
*
* The handler will receive the stanza that triggered it as its argument.
* The handler should return true if it is to be invoked again;
* returning false will remove the handler after it returns.
*
* As a convenience, the ns parameters applies to the top level element
* and also any of its immediate children. This is primarily to make
* matching /iq/query elements easy.
*
* The options argument contains handler matching flags that affect how
* matches are determined. Currently the only flag is matchBare (a
* boolean). When matchBare is true, the from parameter and the from
* attribute on the stanza will be matched as bare JIDs instead of
* full JIDs. To use this, pass {matchBare: true} as the value of
* options. The default value for matchBare is false.
*
* The return value should be saved if you wish to remove the handler
* with deleteHandler().
*
* Parameters:
* (Function) handler - The user callback.
* (String) ns - The namespace to match.
* (String) name - The stanza name to match.
* (String) type - The stanza type attribute to match.
* (String) id - The stanza id attribute to match.
* (String) from - The stanza from attribute to match.
* (String) options - The handler options
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
addHandler: function (handler, ns, name, type, id, from, options)
{
var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
this.addHandlers.push(hand);
return hand;
},
/** Function: deleteHandler
* Delete a stanza handler for a connection.
*
* This function removes a stanza handler from the connection. The
* handRef parameter is *not* the function passed to addHandler(),
* but is the reference returned from addHandler().
*
* Parameters:
* (Strophe.Handler) handRef - The handler reference.
*/
deleteHandler: function (handRef)
{
// this must be done in the Idle loop so that we don't change
// the handlers during iteration
this.removeHandlers.push(handRef);
},
/** Function: disconnect
* Start the graceful disconnection process.
*
* This function starts the disconnection process. This process starts
* by sending unavailable presence and sending BOSH body of type
* terminate. A timeout handler makes sure that disconnection happens
* even if the BOSH server does not respond.
*
* The user supplied connection callback will be notified of the
* progress as this process happens.
*
* Parameters:
* (String) reason - The reason the disconnect is occuring.
*/
disconnect: function(reason) {
if(!this.connected || this._ws == null) {
return;
}
this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
Strophe.info("Disconnect was called because: " + reason);
this._ws.close();
},
/** PrivateFunction: _onDisconnectTimeout
* _Private_ timeout handler for handling non-graceful disconnection.
*
* If the graceful disconnect process does not complete within the
* time allotted, this handler finishes the disconnect anyway.
*
* Returns:
* false to remove the handler.
*/
_onDisconnectTimeout: function ()
{
Strophe.info("_onDisconnectTimeout was called");
this._doDisconnect();
return false;
},
/** PrivateFunction: _doDisconnect
* _Private_ function to disconnect.
*
* This is the last piece of the disconnection logic. This resets the
* connection and alerts the user's connection callback.
*/
_doDisconnect: function ()
{
Strophe.info("_doDisconnect was called");
this._onclose();
},
/** PrivateFunction: _changeConnectStatus
* _Private_ helper function that makes sure plugins and the user's
* callback are notified of connection status changes.
*
* Parameters:
* (Integer) status - the new connection status, one of the values
* in Strophe.Status
* (String) condition - the error condition or null
*/
_changeConnectStatus: function (status, condition)
{
// notify all plugins listening for status changes
for (var k in Strophe._connectionPlugins)
{
if (Strophe._connectionPlugins.hasOwnProperty(k))
{
var plugin = this[k];
if (plugin.statusChanged)
{
try {
plugin.statusChanged(status, condition);
} catch (err) {
Strophe.error("" + k + " plugin caused an exception changing status: " + err);
}
}
}
}
// notify the user's callback
if (typeof this.connect_callback == 'function')
{
try {
this.connect_callback(status, condition);
} catch (e) {
Strophe.error("User connection callback caused an exception: " + e);
}
}
},
/**
*
* Private Function: _onclose websocket event handler
*
*/
_onclose: function()
{
Strophe.info("websocket closed");
//console.log('_onclose - disconnected');
clearInterval(this.interval);
this.authenticated = false;
this.disconnecting = false;
this.streamId = null;
// tell the parent we disconnected
this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
this.connected = false;
// delete handlers
this.handlers = [];
this.timedHandlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
if(this._ws.readyState != this._ws.CLOSED)
{
this._ws.close();
}
},
/**
*
* Private Function: _onmessage websocket event handler
*
*/
_onmessage: function(packet)
{
var elem;
try {
elem = this._textToXML(packet.data);
} catch (e) {
if (e != "parsererror") { throw e; }
this.disconnect("strophe-parsererror");
}
if (elem === null) { return; }
this.xmlInput(elem);
this.rawInput(packet.data);
// remove handlers scheduled for deletion
var i, hand;
while (this.removeHandlers.length > 0)
{
hand = this.removeHandlers.pop();
i = this.handlers.indexOf(hand);
if (i >= 0) {
this.handlers.splice(i, 1);
}
}
// add handlers scheduled for addition
while (this.addHandlers.length > 0)
{
this.handlers.push(this.addHandlers.pop());
}
// send each incoming stanza through the handler chain
var i, newList;
newList = this.handlers;
this.handlers = [];
for (i = 0; i < newList.length; i++)
{
var hand = newList[i];
if (hand.isMatch(elem) && (this.authenticated || !hand.user))
{
if (hand.run(elem))
{
this.handlers.push(hand);
}
} else {
this.handlers.push(hand);
}
}
},
/**
*
* Private Function: _textToXML convert text to DOM Document object
*
*/
_textToXML: function (text) {
var doc = null;
if (window['DOMParser']) {
var parser = new DOMParser();
doc = parser.parseFromString(text, 'text/xml');
} else if (window['ActiveXObject']) {
var doc = new ActiveXObject("MSXML2.DOMDocument");
doc.async = false;
doc.loadXML(text);
} else {
throw Error('No DOMParser object found.');
}
return doc.firstChild;
},
/** PrivateFunction: _onIdle
* _Private_ handler to process events during idle cycle.
*
* This handler is called every 100ms to fire timed handlers that
* are ready and keep poll requests going.
*/
_onIdle: function ()
{
var i, thand, since, newList;
// remove timed handlers that have been scheduled for deletion
while (this.removeTimeds.length > 0)
{
thand = this.removeTimeds.pop();
i = this.timedHandlers.indexOf(thand);
if (i >= 0) {
this.timedHandlers.splice(i, 1);
}
}
// add timed handlers scheduled for addition
while (this.addTimeds.length > 0)
{
this.timedHandlers.push(this.addTimeds.pop());
}
// call ready timed handlers
var now = new Date().getTime();
newList = [];
for (i = 0; i < this.timedHandlers.length; i++)
{
thand = this.timedHandlers[i];
if (this.authenticated || !thand.user) {
since = thand.lastCalled + thand.period;
if (since - now <= 0) {
if (thand.run()) {
newList.push(thand);
}
} else {
newList.push(thand);
}
}
}
this.timedHandlers = newList;
// reactivate the timer
clearTimeout(this._idleTimeout);
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
}
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment