PageSourceSearch

https://www.tweedvalleytrails.org/assets/application-217ca14e5647c…1ee10b546021e6c01026e483a983646f981c3229.js

js tweedvalleytrails.org collected 2026-09-28 13:23:23 UTC 1,027,689 bytes, 33,498 lines download raw bytes

vendor: 4,408 bytes, lines 1-180
1/*!
2 * jQuery JavaScript Library v1.12.4
3 * http://jquery.com/
4 *
5 * Includes Sizzle.js
6 * http://sizzlejs.com/
7 *
8 * Copyright jQuery Foundation and other contributors
9 * Released under the MIT license
10 * http://jquery.org/license
11 *
12 * Date: 2016-05-20T17:17Z
13 */
14
15(function( global, factory ) {
16
17	if ( typeof module === "object" && typeof module.exports === "object" ) {
18		// For CommonJS and CommonJS-like environments where a proper `window`
19		// is present, execute the factory and get jQuery.
20		// For environments that do not have a `window` with a `document`
21		// (such as Node.js), expose a factory as module.exports.
22		// This accentuates the need for the creation of a real `window`.
23		// e.g. var jQuery = require("jquery")(window);
24		// See ticket #14549 for more info.
25		module.exports = global.document ?
26			factory( global, true ) :
27			function( w ) {
28				if ( !w.document ) {
29					throw new Error( "jQuery requires a window with a document" );
30				}
31				return factory( w );
32			};
33	} else {
34		factory( global );
35	}
36
37// Pass this if window is not defined yet
38}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
39
40// Support: Firefox 18+
41// Can't be in strict mode, several libs including ASP.NET trace
42// the stack via arguments.caller.callee and Firefox dies if
43// you try to trace through "use strict" call chains. (#13335)
44//"use strict";
45var deletedIds = [];
46
47var document = window.document;
48
49var slice = deletedIds.slice;
50
51var concat = deletedIds.concat;
52
53var push = deletedIds.push;
54
55var indexOf = deletedIds.indexOf;
56
57var class2type = {};
58
59var toString = class2type.toString;
60
61var hasOwn = class2type.hasOwnProperty;
62
63var support = {};
64
65
66
67var
68	version = "1.12.4",
69
70	// Define a local copy of jQuery
71	jQuery = function( selector, context ) {
72
73		// The jQuery object is actually just the init constructor 'enhanced'
74		// Need init if jQuery is called (just allow error to be thrown if not included)
75		return new jQuery.fn.init( selector, context );
76	},
77
78	// Support: Android<4.1, IE<9
79	// Make sure we trim BOM and NBSP
80	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
81
82	// Matches dashed string for camelizing
83	rmsPrefix = /^-ms-/,
84	rdashAlpha = /-([\da-z])/gi,
85
86	// Used by jQuery.camelCase as callback to replace()
87	fcamelCase = function( all, letter ) {
88		return letter.toUpperCase();
89	};
90
91jQuery.fn = jQuery.prototype = {
92
93	// The current version of jQuery being used
94	jquery: version,
95
96	constructor: jQuery,
97
98	// Start with an empty selector
99	selector: "",
100
101	// The default length of a jQuery object is 0
102	length: 0,
103
104	toArray: function() {
105		return slice.call( this );
106	},
107
108	// Get the Nth element in the matched element set OR
109	// Get the whole matched element set as a clean array
110	get: function( num ) {
111		return num != null ?
112
113			// Return just the one element from the set
114			( num < 0 ? this[ num + this.length ] : this[ num ] ) :
115
116			// Return all the elements in a clean array
117			slice.call( this );
118	},
119
120	// Take an array of elements and push it onto the stack
121	// (returning the new matched element set)
122	pushStack: function( elems ) {
123
124		// Build a new jQuery matched element set
125		var ret = jQuery.merge( this.constructor(), elems );
126
127		// Add the old object onto the stack (as a reference)
128		ret.prevObject = this;
129		ret.context = this.context;
130
131		// Return the newly-formed element set
132		return ret;
133	},
134
135	// Execute a callback for every element in the matched set.
136	each: function( callback ) {
137		return jQuery.each( this, callback );
138	},
139
140	map: function( callback ) {
141		return this.pushStack( jQuery.map( this, function( elem, i ) {
142			return callback.call( elem, i, elem );
143		} ) );
144	},
145
146	slice: function() {
147		return this.pushStack( slice.apply( this, arguments ) );
148	},
149
150	first: function() {
151		return this.eq( 0 );
152	},
153
154	last: function() {
155		return this.eq( -1 );
156	},
157
158	eq: function( i ) {
159		var len = this.length,
160			j = +i + ( i < 0 ? len : 0 );
161		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
162	},
163
164	end: function() {
165		return this.prevObject || this.constructor();
166	},
167
168	// For internal use only.
169	// Behaves like an Array's method, not like a jQuery method.
170	push: push,
171	sort: deletedIds.sort,
172	splice: deletedIds.splice
173};
174
175jQuery.extend = jQuery.fn.extend = function() {
176	var src, copyIsArray, copy, name, options, clone,
177		target = arguments[ 0 ] || {},
178		i = 1,
179		length = arguments.length,
180		deep = false;
vendor: 8,863 bytes, lines 181-553
181
182	// Handle a deep copy situation
183	if ( typeof target === "boolean" ) {
184		deep = target;
185
186		// skip the boolean and the target
187		target = arguments[ i ] || {};
188		i++;
189	}
190
191	// Handle case when target is a string or something (possible in deep copy)
192	if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
193		target = {};
194	}
195
196	// extend jQuery itself if only one argument is passed
197	if ( i === length ) {
198		target = this;
199		i--;
200	}
201
202	for ( ; i < length; i++ ) {
203
204		// Only deal with non-null/undefined values
205		if ( ( options = arguments[ i ] ) != null ) {
206
207			// Extend the base object
208			for ( name in options ) {
209				src = target[ name ];
210				copy = options[ name ];
211
212				// Prevent never-ending loop
213				if ( target === copy ) {
214					continue;
215				}
216
217				// Recurse if we're merging plain objects or arrays
218				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
219					( copyIsArray = jQuery.isArray( copy ) ) ) ) {
220
221					if ( copyIsArray ) {
222						copyIsArray = false;
223						clone = src && jQuery.isArray( src ) ? src : [];
224
225					} else {
226						clone = src && jQuery.isPlainObject( src ) ? src : {};
227					}
228
229					// Never move original objects, clone them
230					target[ name ] = jQuery.extend( deep, clone, copy );
231
232				// Don't bring in undefined values
233				} else if ( copy !== undefined ) {
234					target[ name ] = copy;
235				}
236			}
237		}
238	}
239
240	// Return the modified object
241	return target;
242};
243
244jQuery.extend( {
245
246	// Unique for each copy of jQuery on the page
247	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
248
249	// Assume jQuery is ready without the ready module
250	isReady: true,
251
252	error: function( msg ) {
253		throw new Error( msg );
254	},
255
256	noop: function() {},
257
258	// See test/unit/core.js for details concerning isFunction.
259	// Since version 1.3, DOM methods and functions like alert
260	// aren't supported. They return false on IE (#2968).
261	isFunction: function( obj ) {
262		return jQuery.type( obj ) === "function";
263	},
264
265	isArray: Array.isArray || function( obj ) {
266		return jQuery.type( obj ) === "array";
267	},
268
269	isWindow: function( obj ) {
270		/* jshint eqeqeq: false */
271		return obj != null && obj == obj.window;
272	},
273
274	isNumeric: function( obj ) {
275
276		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
277		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
278		// subtraction forces infinities to NaN
279		// adding 1 corrects loss of precision from parseFloat (#15100)
280		var realStringObj = obj && obj.toString();
281		return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
282	},
283
284	isEmptyObject: function( obj ) {
285		var name;
286		for ( name in obj ) {
287			return false;
288		}
289		return true;
290	},
291
292	isPlainObject: function( obj ) {
293		var key;
294
295		// Must be an Object.
296		// Because of IE, we also have to check the presence of the constructor property.
297		// Make sure that DOM nodes and window objects don't pass through, as well
298		if ( !obj || jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
299			return false;
300		}
301
302		try {
303
304			// Not own constructor property must be Object
305			if ( obj.constructor &&
306				!hasOwn.call( obj, "constructor" ) &&
307				!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
308				return false;
309			}
310		} catch ( e ) {
311
312			// IE8,9 Will throw exceptions on certain host objects #9897
313			return false;
314		}
315
316		// Support: IE<9
317		// Handle iteration over inherited properties before own properties.
318		if ( !support.ownFirst ) {
319			for ( key in obj ) {
320				return hasOwn.call( obj, key );
321			}
322		}
323
324		// Own properties are enumerated firstly, so to speed up,
325		// if last one is own, then all properties are own.
326		for ( key in obj ) {}
327
328		return key === undefined || hasOwn.call( obj, key );
329	},
330
331	type: function( obj ) {
332		if ( obj == null ) {
333			return obj + "";
334		}
335		return typeof obj === "object" || typeof obj === "function" ?
336			class2type[ toString.call( obj ) ] || "object" :
337			typeof obj;
338	},
339
340	// Workarounds based on findings by Jim Driscoll
341	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
342	globalEval: function( data ) {
343		if ( data && jQuery.trim( data ) ) {
344
345			// We use execScript on Internet Explorer
346			// We use an anonymous function so that context is window
347			// rather than jQuery in Firefox
348			( window.execScript || function( data ) {
349				window[ "eval" ].call( window, data ); // jscs:ignore requireDotNotation
350			} )( data );
351		}
352	},
353
354	// Convert dashed to camelCase; used by the css and data modules
355	// Microsoft forgot to hump their vendor prefix (#9572)
356	camelCase: function( string ) {
357		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
358	},
359
360	nodeName: function( elem, name ) {
361		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
362	},
363
364	each: function( obj, callback ) {
365		var length, i = 0;
366
367		if ( isArrayLike( obj ) ) {
368			length = obj.length;
369			for ( ; i < length; i++ ) {
370				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
371					break;
372				}
373			}
374		} else {
375			for ( i in obj ) {
376				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
377					break;
378				}
379			}
380		}
381
382		return obj;
383	},
384
385	// Support: Android<4.1, IE<9
386	trim: function( text ) {
387		return text == null ?
388			"" :
389			( text + "" ).replace( rtrim, "" );
390	},
391
392	// results is for internal usage only
393	makeArray: function( arr, results ) {
394		var ret = results || [];
395
396		if ( arr != null ) {
397			if ( isArrayLike( Object( arr ) ) ) {
398				jQuery.merge( ret,
399					typeof arr === "string" ?
400					[ arr ] : arr
401				);
402			} else {
403				push.call( ret, arr );
404			}
405		}
406
407		return ret;
408	},
409
410	inArray: function( elem, arr, i ) {
411		var len;
412
413		if ( arr ) {
414			if ( indexOf ) {
415				return indexOf.call( arr, elem, i );
416			}
417
418			len = arr.length;
419			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
420
421			for ( ; i < len; i++ ) {
422
423				// Skip accessing in sparse arrays
424				if ( i in arr && arr[ i ] === elem ) {
425					return i;
426				}
427			}
428		}
429
430		return -1;
431	},
432
433	merge: function( first, second ) {
434		var len = +second.length,
435			j = 0,
436			i = first.length;
437
438		while ( j < len ) {
439			first[ i++ ] = second[ j++ ];
440		}
441
442		// Support: IE<9
443		// Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
444		if ( len !== len ) {
445			while ( second[ j ] !== undefined ) {
446				first[ i++ ] = second[ j++ ];
447			}
448		}
449
450		first.length = i;
451
452		return first;
453	},
454
455	grep: function( elems, callback, invert ) {
456		var callbackInverse,
457			matches = [],
458			i = 0,
459			length = elems.length,
460			callbackExpect = !invert;
461
462		// Go through the array, only saving the items
463		// that pass the validator function
464		for ( ; i < length; i++ ) {
465			callbackInverse = !callback( elems[ i ], i );
466			if ( callbackInverse !== callbackExpect ) {
467				matches.push( elems[ i ] );
468			}
469		}
470
471		return matches;
472	},
473
474	// arg is for internal usage only
475	map: function( elems, callback, arg ) {
476		var length, value,
477			i = 0,
478			ret = [];
479
480		// Go through the array, translating each of the items to their new values
481		if ( isArrayLike( elems ) ) {
482			length = elems.length;
483			for ( ; i < length; i++ ) {
484				value = callback( elems[ i ], i, arg );
485
486				if ( value != null ) {
487					ret.push( value );
488				}
489			}
490
491		// Go through every key on the object,
492		} else {
493			for ( i in elems ) {
494				value = callback( elems[ i ], i, arg );
495
496				if ( value != null ) {
497					ret.push( value );
498				}
499			}
500		}
501
502		// Flatten any nested arrays
503		return concat.apply( [], ret );
504	},
505
506	// A global GUID counter for objects
507	guid: 1,
508
509	// Bind a function to a context, optionally partially applying any
510	// arguments.
511	proxy: function( fn, context ) {
512		var args, proxy, tmp;
513
514		if ( typeof context === "string" ) {
515			tmp = fn[ context ];
516			context = fn;
517			fn = tmp;
518		}
519
520		// Quick check to determine if target is callable, in the spec
521		// this throws a TypeError, but we will just return undefined.
522		if ( !jQuery.isFunction( fn ) ) {
523			return undefined;
524		}
525
526		// Simulated bind
527		args = slice.call( arguments, 2 );
528		proxy = function() {
529			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
530		};
531
532		// Set the guid of unique handler to the same of original handler, so it can be removed
533		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
534
535		return proxy;
536	},
537
538	now: function() {
539		return +( new Date() );
540	},
541
542	// jQuery.support is not used in Core but other projects attach their
543	// properties to it so it needs to exist.
544	support: support
545} );
546
547// JSHint would error on this code due to the Symbol not being defined in ES5.
548// Defining this global in .jshintrc would create a danger of using the global
549// unguarded in another place, it seems safer to just disable JSHint for these
550// three lines.
551/* jshint ignore: start */
552if ( typeof Symbol === "function" ) {
553	jQuery.fn[ Symbol.iterator ] = deletedIds
vendor: 63,327 bytes, lines 553-2790
553[ Symbol.iterator ];
554}
555/* jshint ignore: end */
556
557// Populate the class2type map
558jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
559function( i, name ) {
560	class2type[ "[object " + name + "]" ] = name.toLowerCase();
561} );
562
563function isArrayLike( obj ) {
564
565	// Support: iOS 8.2 (not reproducible in simulator)
566	// `in` check used to prevent JIT error (gh-2145)
567	// hasOwn isn't used here due to false negatives
568	// regarding Nodelist length in IE
569	var length = !!obj && "length" in obj && obj.length,
570		type = jQuery.type( obj );
571
572	if ( type === "function" || jQuery.isWindow( obj ) ) {
573		return false;
574	}
575
576	return type === "array" || length === 0 ||
577		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
578}
579var Sizzle =
580/*!
581 * Sizzle CSS Selector Engine v2.2.1
582 * http://sizzlejs.com/
583 *
584 * Copyright jQuery Foundation and other contributors
585 * Released under the MIT license
586 * http://jquery.org/license
587 *
588 * Date: 2015-10-17
589 */
590(function( window ) {
591
592var i,
593	support,
594	Expr,
595	getText,
596	isXML,
597	tokenize,
598	compile,
599	select,
600	outermostContext,
601	sortInput,
602	hasDuplicate,
603
604	// Local document vars
605	setDocument,
606	document,
607	docElem,
608	documentIsHTML,
609	rbuggyQSA,
610	rbuggyMatches,
611	matches,
612	contains,
613
614	// Instance-specific data
615	expando = "sizzle" + 1 * new Date(),
616	preferredDoc = window.document,
617	dirruns = 0,
618	done = 0,
619	classCache = createCache(),
620	tokenCache = createCache(),
621	compilerCache = createCache(),
622	sortOrder = function( a, b ) {
623		if ( a === b ) {
624			hasDuplicate = true;
625		}
626		return 0;
627	},
628
629	// General-purpose constants
630	MAX_NEGATIVE = 1 << 31,
631
632	// Instance methods
633	hasOwn = ({}).hasOwnProperty,
634	arr = [],
635	pop = arr.pop,
636	push_native = arr.push,
637	push = arr.push,
638	slice = arr.slice,
639	// Use a stripped-down indexOf as it's faster than native
640	// http://jsperf.com/thor-indexof-vs-for/5
641	indexOf = function( list, elem ) {
642		var i = 0,
643			len = list.length;
644		for ( ; i < len; i++ ) {
645			if ( list[i] === elem ) {
646				return i;
647			}
648		}
649		return -1;
650	},
651
652	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
653
654	// Regular expressions
655
656	// http://www.w3.org/TR/css3-selectors/#whitespace
657	whitespace = "[\\x20\\t\\r\\n\\f]",
658
659	// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
660	identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
661
662	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
663	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
664		// Operator (capture 2)
665		"*([*^$|!~]?=)" + whitespace +
666		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
667		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
668		"*\\]",
669
670	pseudos = ":(" + identifier + ")(?:\\((" +
671		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
672		// 1. quoted (capture 3; capture 4 or capture 5)
673		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
674		// 2. simple (capture 6)
675		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
676		// 3. anything else (capture 2)
677		".*" +
678		")\\)|)",
679
680	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
681	rwhitespace = new RegExp( whitespace + "+", "g" ),
682	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
683
684	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
685	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
686
687	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
688
689	rpseudo = new RegExp( pseudos ),
690	ridentifier = new RegExp( "^" + identifier + "$" ),
691
692	matchExpr = {
693		"ID": new RegExp( "^#(" + identifier + ")" ),
694		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
695		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
696		"ATTR": new RegExp( "^" + attributes ),
697		"PSEUDO": new RegExp( "^" + pseudos ),
698		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
699			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
700			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
701		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
702		// For use in libraries implementing .is()
703		// We use this for POS matching in `select`
704		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
705			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
706	},
707
708	rinputs = /^(?:input|select|textarea|button)$/i,
709	rheader = /^h\d$/i,
710
711	rnative = /^[^{]+\{\s*\[native \w/,
712
713	// Easily-parseable/retrievable ID or TAG or CLASS selectors
714	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
715
716	rsibling = /[+~]/,
717	rescape = /'|\\/g,
718
719	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
720	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
721	funescape = function( _, escaped, escapedWhitespace ) {
722		var high = "0x" + escaped - 0x10000;
723		// NaN means non-codepoint
724		// Support: Firefox<24
725		// Workaround erroneous numeric interpretation of +"0x"
726		return high !== high || escapedWhitespace ?
727			escaped :
728			high < 0 ?
729				// BMP codepoint
730				String.fromCharCode( high + 0x10000 ) :
731				// Supplemental Plane codepoint (surrogate pair)
732				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
733	},
734
735	// Used for iframes
736	// See setDocument()
737	// Removing the function wrapper causes a "Permission Denied"
738	// error in IE
739	unloadHandler = function() {
740		setDocument();
741	};
742
743// Optimize for push.apply( _, NodeList )
744try {
745	push.apply(
746		(arr = slice.call( preferredDoc.childNodes )),
747		preferredDoc.childNodes
748	);
749	// Support: Android<4.0
750	// Detect silently failing push.apply
751	arr[ preferredDoc.childNodes.length ].nodeType;
752} catch ( e ) {
753	push = { apply: arr.length ?
754
755		// Leverage slice if possible
756		function( target, els ) {
757			push_native.apply( target, slice.call(els) );
758		} :
759
760		// Support: IE<9
761		// Otherwise append directly
762		function( target, els ) {
763			var j = target.length,
764				i = 0;
765			// Can't trust NodeList.length
766			while ( (target[j++] = els[i++]) ) {}
767			target.length = j - 1;
768		}
769	};
770}
771
772function Sizzle( selector, context, results, seed ) {
773	var m, i, elem, nid, nidselect, match, groups, newSelector,
774		newContext = context && context.ownerDocument,
775
776		// nodeType defaults to 9, since context defaults to document
777		nodeType = context ? context.nodeType : 9;
778
779	results = results || [];
780
781	// Return early from calls with invalid selector or context
782	if ( typeof selector !== "string" || !selector ||
783		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
784
785		return results;
786	}
787
788	// Try to shortcut find operations (as opposed to filters) in HTML documents
789	if ( !seed ) {
790
791		if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
792			setDocument( context );
793		}
794		context = context || document;
795
796		if ( documentIsHTML ) {
797
798			// If the selector is sufficiently simple, try using a "get*By*" DOM method
799			// (excepting DocumentFragment context, where the methods don't exist)
800			if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
801
802				// ID selector
803				if ( (m = match[1]) ) {
804
805					// Document context
806					if ( nodeType === 9 ) {
807						if ( (elem = context.getElementById( m )) ) {
808
809							// Support: IE, Opera, Webkit
810							// TODO: identify versions
811							// getElementById can match elements by name instead of ID
812							if ( elem.id === m ) {
813								results.push( elem );
814								return results;
815							}
816						} else {
817							return results;
818						}
819
820					// Element context
821					} else {
822
823						// Support: IE, Opera, Webkit
824						// TODO: identify versions
825						// getElementById can match elements by name instead of ID
826						if ( newContext && (elem = newContext.getElementById( m )) &&
827							contains( context, elem ) &&
828							elem.id === m ) {
829
830							results.push( elem );
831							return results;
832						}
833					}
834
835				// Type selector
836				} else if ( match[2] ) {
837					push.apply( results, context.getElementsByTagName( selector ) );
838					return results;
839
840				// Class selector
841				} else if ( (m = match[3]) && support.getElementsByClassName &&
842					context.getElementsByClassName ) {
843
844					push.apply( results, context.getElementsByClassName( m ) );
845					return results;
846				}
847			}
848
849			// Take advantage of querySelectorAll
850			if ( support.qsa &&
851				!compilerCache[ selector + " " ] &&
852				(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
853
854				if ( nodeType !== 1 ) {
855					newContext = context;
856					newSelector = selector;
857
858				// qSA looks outside Element context, which is not what we want
859				// Thanks to Andrew Dupont for this workaround technique
860				// Support: IE <=8
861				// Exclude object elements
862				} else if ( context.nodeName.toLowerCase() !== "object" ) {
863
864					// Capture the context ID, setting it first if necessary
865					if ( (nid = context.getAttribute( "id" )) ) {
866						nid = nid.replace( rescape, "\\$&" );
867					} else {
868						context.setAttribute( "id", (nid = expando) );
869					}
870
871					// Prefix every selector in the list
872					groups = tokenize( selector );
873					i = groups.length;
874					nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
875					while ( i-- ) {
876						groups[i] = nidselect + " " + toSelector( groups[i] );
877					}
878					newSelector = groups.join( "," );
879
880					// Expand context for sibling selectors
881					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
882						context;
883				}
884
885				if ( newSelector ) {
886					try {
887						push.apply( results,
888							newContext.querySelectorAll( newSelector )
889						);
890						return results;
891					} catch ( qsaError ) {
892					} finally {
893						if ( nid === expando ) {
894							context.removeAttribute( "id" );
895						}
896					}
897				}
898			}
899		}
900	}
901
902	// All others
903	return select( selector.replace( rtrim, "$1" ), context, results, seed );
904}
905
906/**
907 * Create key-value caches of limited size
908 * @returns {function(string, object)} Returns the Object data after storing it on itself with
909 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
910 *	deleting the oldest entry
911 */
912function createCache() {
913	var keys = [];
914
915	function cache( key, value ) {
916		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
917		if ( keys.push( key + " " ) > Expr.cacheLength ) {
918			// Only keep the most recent entries
919			delete cache[ keys.shift() ];
920		}
921		return (cache[ key + " " ] = value);
922	}
923	return cache;
924}
925
926/**
927 * Mark a function for special use by Sizzle
928 * @param {Function} fn The function to mark
929 */
930function markFunction( fn ) {
931	fn[ expando ] = true;
932	return fn;
933}
934
935/**
936 * Support testing using an element
937 * @param {Function} fn Passed the created div and expects a boolean result
938 */
939function assert( fn ) {
940	var div = document.createElement("div");
941
942	try {
943		return !!fn( div );
944	} catch (e) {
945		return false;
946	} finally {
947		// Remove from its parent by default
948		if ( div.parentNode ) {
949			div.parentNode.removeChild( div );
950		}
951		// release memory in IE
952		div = null;
953	}
954}
955
956/**
957 * Adds the same handler for all of the specified attrs
958 * @param {String} attrs Pipe-separated list of attributes
959 * @param {Function} handler The method that will be applied
960 */
961function addHandle( attrs, handler ) {
962	var arr = attrs.split("|"),
963		i = arr.length;
964
965	while ( i-- ) {
966		Expr.attrHandle[ arr[i] ] = handler;
967	}
968}
969
970/**
971 * Checks document order of two siblings
972 * @param {Element} a
973 * @param {Element} b
974 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
975 */
976function siblingCheck( a, b ) {
977	var cur = b && a,
978		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
979			( ~b.sourceIndex || MAX_NEGATIVE ) -
980			( ~a.sourceIndex || MAX_NEGATIVE );
981
982	// Use IE sourceIndex if available on both nodes
983	if ( diff ) {
984		return diff;
985	}
986
987	// Check if b follows a
988	if ( cur ) {
989		while ( (cur = cur.nextSibling) ) {
990			if ( cur === b ) {
991				return -1;
992			}
993		}
994	}
995
996	return a ? 1 : -1;
997}
998
999/**
1000 * Returns a function to use in pseudos for input types
1001 * @param {String} type
1002 */
1003function createInputPseudo( type ) {
1004	return function( elem ) {
1005		var name = elem.nodeName.toLowerCase();
1006		return name === "input" && elem.type === type;
1007	};
1008}
1009
1010/**
1011 * Returns a function to use in pseudos for buttons
1012 * @param {String} type
1013 */
1014function createButtonPseudo( type ) {
1015	return function( elem ) {
1016		var name = elem.nodeName.toLowerCase();
1017		return (name === "input" || name === "button") && elem.type === type;
1018	};
1019}
1020
1021/**
1022 * Returns a function to use in pseudos for positionals
1023 * @param {Function} fn
1024 */
1025function createPositionalPseudo( fn ) {
1026	return markFunction(function( argument ) {
1027		argument = +argument;
1028		return markFunction(function( seed, matches ) {
1029			var j,
1030				matchIndexes = fn( [], seed.length, argument ),
1031				i = matchIndexes.length;
1032
1033			// Match elements found at the specified indexes
1034			while ( i-- ) {
1035				if ( seed[ (j = matchIndexes[i]) ] ) {
1036					seed[j] = !(matches[j] = seed[j]);
1037				}
1038			}
1039		});
1040	});
1041}
1042
1043/**
1044 * Checks a node for validity as a Sizzle context
1045 * @param {Element|Object=} context
1046 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
1047 */
1048function testContext( context ) {
1049	return context && typeof context.getElementsByTagName !== "undefined" && context;
1050}
1051
1052// Expose support vars for convenience
1053support = Sizzle.support = {};
1054
1055/**
1056 * Detects XML nodes
1057 * @param {Element|Object} elem An element or a document
1058 * @returns {Boolean} True iff elem is a non-HTML XML node
1059 */
1060isXML = Sizzle.isXML = function( elem ) {
1061	// documentElement is verified for cases where it doesn't yet exist
1062	// (such as loading iframes in IE - #4833)
1063	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
1064	return documentElement ? documentElement.nodeName !== "HTML" : false;
1065};
1066
1067/**
1068 * Sets document-related variables once based on the current document
1069 * @param {Element|Object} [doc] An element or document object to use to set the document
1070 * @returns {Object} Returns the current document
1071 */
1072setDocument = Sizzle.setDocument = function( node ) {
1073	var hasCompare, parent,
1074		doc = node ? node.ownerDocument || node : preferredDoc;
1075
1076	// Return early if doc is invalid or already selected
1077	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
1078		return document;
1079	}
1080
1081	// Update global variables
1082	document = doc;
1083	docElem = document.documentElement;
1084	documentIsHTML = !isXML( document );
1085
1086	// Support: IE 9-11, Edge
1087	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
1088	if ( (parent = document.defaultView) && parent.top !== parent ) {
1089		// Support: IE 11
1090		if ( parent.addEventListener ) {
1091			parent.addEventListener( "unload", unloadHandler, false );
1092
1093		// Support: IE 9 - 10 only
1094		} else if ( parent.attachEvent ) {
1095			parent.attachEvent( "onunload", unloadHandler );
1096		}
1097	}
1098
1099	/* Attributes
1100	---------------------------------------------------------------------- */
1101
1102	// Support: IE<8
1103	// Verify that getAttribute really returns attributes and not properties
1104	// (excepting IE8 booleans)
1105	support.attributes = assert(function( div ) {
1106		div.className = "i";
1107		return !div.getAttribute("className");
1108	});
1109
1110	/* getElement(s)By*
1111	---------------------------------------------------------------------- */
1112
1113	// Check if getElementsByTagName("*") returns only elements
1114	support.getElementsByTagName = assert(function( div ) {
1115		div.appendChild( document.createComment("") );
1116		return !div.getElementsByTagName("*").length;
1117	});
1118
1119	// Support: IE<9
1120	support.getElementsByClassName = rnative.test( document.getElementsByClassName );
1121
1122	// Support: IE<10
1123	// Check if getElementById returns elements by name
1124	// The broken getElementById methods don't pick up programatically-set names,
1125	// so use a roundabout getElementsByName test
1126	support.getById = assert(function( div ) {
1127		docElem.appendChild( div ).id = expando;
1128		return !document.getElementsByName || !document.getElementsByName( expando ).length;
1129	});
1130
1131	// ID find and filter
1132	if ( support.getById ) {
1133		Expr.find["ID"] = function( id, context ) {
1134			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
1135				var m = context.getElementById( id );
1136				return m ? [ m ] : [];
1137			}
1138		};
1139		Expr.filter["ID"] = function( id ) {
1140			var attrId = id.replace( runescape, funescape );
1141			return function( elem ) {
1142				return elem.getAttribute("id") === attrId;
1143			};
1144		};
1145	} else {
1146		// Support: IE6/7
1147		// getElementById is not reliable as a find shortcut
1148		delete Expr.find["ID"];
1149
1150		Expr.filter["ID"] =  function( id ) {
1151			var attrId = id.replace( runescape, funescape );
1152			return function( elem ) {
1153				var node = typeof elem.getAttributeNode !== "undefined" &&
1154					elem.getAttributeNode("id");
1155				return node && node.value === attrId;
1156			};
1157		};
1158	}
1159
1160	// Tag
1161	Expr.find["TAG"] = support.getElementsByTagName ?
1162		function( tag, context ) {
1163			if ( typeof context.getElementsByTagName !== "undefined" ) {
1164				return context.getElementsByTagName( tag );
1165
1166			// DocumentFragment nodes don't have gEBTN
1167			} else if ( support.qsa ) {
1168				return context.querySelectorAll( tag );
1169			}
1170		} :
1171
1172		function( tag, context ) {
1173			var elem,
1174				tmp = [],
1175				i = 0,
1176				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
1177				results = context.getElementsByTagName( tag );
1178
1179			// Filter out possible comments
1180			if ( tag === "*" ) {
1181				while ( (elem = results[i++]) ) {
1182					if ( elem.nodeType === 1 ) {
1183						tmp.push( elem );
1184					}
1185				}
1186
1187				return tmp;
1188			}
1189			return results;
1190		};
1191
1192	// Class
1193	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
1194		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
1195			return context.getElementsByClassName( className );
1196		}
1197	};
1198
1199	/* QSA/matchesSelector
1200	---------------------------------------------------------------------- */
1201
1202	// QSA and matchesSelector support
1203
1204	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
1205	rbuggyMatches = [];
1206
1207	// qSa(:focus) reports false when true (Chrome 21)
1208	// We allow this because of a bug in IE8/9 that throws an error
1209	// whenever `document.activeElement` is accessed on an iframe
1210	// So, we allow :focus to pass through QSA all the time to avoid the IE error
1211	// See http://bugs.jquery.com/ticket/13378
1212	rbuggyQSA = [];
1213
1214	if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
1215		// Build QSA regex
1216		// Regex strategy adopted from Diego Perini
1217		assert(function( div ) {
1218			// Select is set to empty string on purpose
1219			// This is to test IE's treatment of not explicitly
1220			// setting a boolean content attribute,
1221			// since its presence should be enough
1222			// http://bugs.jquery.com/ticket/12359
1223			docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" +
1224				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
1225				"<option selected=''></option></select>";
1226
1227			// Support: IE8, Opera 11-12.16
1228			// Nothing should be selected when empty strings follow ^= or $= or *=
1229			// The test attribute must be unknown in Opera but "safe" for WinRT
1230			// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
1231			if ( div.querySelectorAll("[msallowcapture^='']").length ) {
1232				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
1233			}
1234
1235			// Support: IE8
1236			// Boolean attributes and "value" are not treated correctly
1237			if ( !div.querySelectorAll("[selected]").length ) {
1238				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
1239			}
1240
1241			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
1242			if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
1243				rbuggyQSA.push("~=");
1244			}
1245
1246			// Webkit/Opera - :checked should return selected option elements
1247			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
1248			// IE8 throws error here and will not see later tests
1249			if ( !div.querySelectorAll(":checked").length ) {
1250				rbuggyQSA.push(":checked");
1251			}
1252
1253			// Support: Safari 8+, iOS 8+
1254			// https://bugs.webkit.org/show_bug.cgi?id=136851
1255			// In-page `selector#id sibing-combinator selector` fails
1256			if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
1257				rbuggyQSA.push(".#.+[+~]");
1258			}
1259		});
1260
1261		assert(function( div ) {
1262			// Support: Windows 8 Native Apps
1263			// The type and name attributes are restricted during .innerHTML assignment
1264			var input = document.createElement("input");
1265			input.setAttribute( "type", "hidden" );
1266			div.appendChild( input ).setAttribute( "name", "D" );
1267
1268			// Support: IE8
1269			// Enforce case-sensitivity of name attribute
1270			if ( div.querySelectorAll("[name=d]").length ) {
1271				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
1272			}
1273
1274			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
1275			// IE8 throws error here and will not see later tests
1276			if ( !div.querySelectorAll(":enabled").length ) {
1277				rbuggyQSA.push( ":enabled", ":disabled" );
1278			}
1279
1280			// Opera 10-11 does not throw on post-comma invalid pseudos
1281			div.querySelectorAll("*,:x");
1282			rbuggyQSA.push(",.*:");
1283		});
1284	}
1285
1286	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
1287		docElem.webkitMatchesSelector ||
1288		docElem.mozMatchesSelector ||
1289		docElem.oMatchesSelector ||
1290		docElem.msMatchesSelector) )) ) {
1291
1292		assert(function( div ) {
1293			// Check to see if it's possible to do matchesSelector
1294			// on a disconnected node (IE 9)
1295			support.disconnectedMatch = matches.call( div, "div" );
1296
1297			// This should fail with an exception
1298			// Gecko does not error, returns false instead
1299			matches.call( div, "[s!='']:x" );
1300			rbuggyMatches.push( "!=", pseudos );
1301		});
1302	}
1303
1304	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
1305	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
1306
1307	/* Contains
1308	---------------------------------------------------------------------- */
1309	hasCompare = rnative.test( docElem.compareDocumentPosition );
1310
1311	// Element contains another
1312	// Purposefully self-exclusive
1313	// As in, an element does not contain itself
1314	contains = hasCompare || rnative.test( docElem.contains ) ?
1315		function( a, b ) {
1316			var adown = a.nodeType === 9 ? a.documentElement : a,
1317				bup = b && b.parentNode;
1318			return a === bup || !!( bup && bup.nodeType === 1 && (
1319				adown.contains ?
1320					adown.contains( bup ) :
1321					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
1322			));
1323		} :
1324		function( a, b ) {
1325			if ( b ) {
1326				while ( (b = b.parentNode) ) {
1327					if ( b === a ) {
1328						return true;
1329					}
1330				}
1331			}
1332			return false;
1333		};
1334
1335	/* Sorting
1336	---------------------------------------------------------------------- */
1337
1338	// Document order sorting
1339	sortOrder = hasCompare ?
1340	function( a, b ) {
1341
1342		// Flag for duplicate removal
1343		if ( a === b ) {
1344			hasDuplicate = true;
1345			return 0;
1346		}
1347
1348		// Sort on method existence if only one input has compareDocumentPosition
1349		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
1350		if ( compare ) {
1351			return compare;
1352		}
1353
1354		// Calculate position if both inputs belong to the same document
1355		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
1356			a.compareDocumentPosition( b ) :
1357
1358			// Otherwise we know they are disconnected
1359			1;
1360
1361		// Disconnected nodes
1362		if ( compare & 1 ||
1363			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
1364
1365			// Choose the first element that is related to our preferred document
1366			if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
1367				return -1;
1368			}
1369			if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
1370				return 1;
1371			}
1372
1373			// Maintain original order
1374			return sortInput ?
1375				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
1376				0;
1377		}
1378
1379		return compare & 4 ? -1 : 1;
1380	} :
1381	function( a, b ) {
1382		// Exit early if the nodes are identical
1383		if ( a === b ) {
1384			hasDuplicate = true;
1385			return 0;
1386		}
1387
1388		var cur,
1389			i = 0,
1390			aup = a.parentNode,
1391			bup = b.parentNode,
1392			ap = [ a ],
1393			bp = [ b ];
1394
1395		// Parentless nodes are either documents or disconnected
1396		if ( !aup || !bup ) {
1397			return a === document ? -1 :
1398				b === document ? 1 :
1399				aup ? -1 :
1400				bup ? 1 :
1401				sortInput ?
1402				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
1403				0;
1404
1405		// If the nodes are siblings, we can do a quick check
1406		} else if ( aup === bup ) {
1407			return siblingCheck( a, b );
1408		}
1409
1410		// Otherwise we need full lists of their ancestors for comparison
1411		cur = a;
1412		while ( (cur = cur.parentNode) ) {
1413			ap.unshift( cur );
1414		}
1415		cur = b;
1416		while ( (cur = cur.parentNode) ) {
1417			bp.unshift( cur );
1418		}
1419
1420		// Walk down the tree looking for a discrepancy
1421		while ( ap[i] === bp[i] ) {
1422			i++;
1423		}
1424
1425		return i ?
1426			// Do a sibling check if the nodes have a common ancestor
1427			siblingCheck( ap[i], bp[i] ) :
1428
1429			// Otherwise nodes in our document sort first
1430			ap[i] === preferredDoc ? -1 :
1431			bp[i] === preferredDoc ? 1 :
1432			0;
1433	};
1434
1435	return document;
1436};
1437
1438Sizzle.matches = function( expr, elements ) {
1439	return Sizzle( expr, null, null, elements );
1440};
1441
1442Sizzle.matchesSelector = function( elem, expr ) {
1443	// Set document vars if needed
1444	if ( ( elem.ownerDocument || elem ) !== document ) {
1445		setDocument( elem );
1446	}
1447
1448	// Make sure that attribute selectors are quoted
1449	expr = expr.replace( rattributeQuotes, "='$1']" );
1450
1451	if ( support.matchesSelector && documentIsHTML &&
1452		!compilerCache[ expr + " " ] &&
1453		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
1454		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
1455
1456		try {
1457			var ret = matches.call( elem, expr );
1458
1459			// IE 9's matchesSelector returns false on disconnected nodes
1460			if ( ret || support.disconnectedMatch ||
1461					// As well, disconnected nodes are said to be in a document
1462					// fragment in IE 9
1463					elem.document && elem.document.nodeType !== 11 ) {
1464				return ret;
1465			}
1466		} catch (e) {}
1467	}
1468
1469	return Sizzle( expr, document, null, [ elem ] ).length > 0;
1470};
1471
1472Sizzle.contains = function( context, elem ) {
1473	// Set document vars if needed
1474	if ( ( context.ownerDocument || context ) !== document ) {
1475		setDocument( context );
1476	}
1477	return contains( context, elem );
1478};
1479
1480Sizzle.attr = function( elem, name ) {
1481	// Set document vars if needed
1482	if ( ( elem.ownerDocument || elem ) !== document ) {
1483		setDocument( elem );
1484	}
1485
1486	var fn = Expr.attrHandle[ name.toLowerCase() ],
1487		// Don't get fooled by Object.prototype properties (jQuery #13807)
1488		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
1489			fn( elem, name, !documentIsHTML ) :
1490			undefined;
1491
1492	return val !== undefined ?
1493		val :
1494		support.attributes || !documentIsHTML ?
1495			elem.getAttribute( name ) :
1496			(val = elem.getAttributeNode(name)) && val.specified ?
1497				val.value :
1498				null;
1499};
1500
1501Sizzle.error = function( msg ) {
1502	throw new Error( "Syntax error, unrecognized expression: " + msg );
1503};
1504
1505/**
1506 * Document sorting and removing duplicates
1507 * @param {ArrayLike} results
1508 */
1509Sizzle.uniqueSort = function( results ) {
1510	var elem,
1511		duplicates = [],
1512		j = 0,
1513		i = 0;
1514
1515	// Unless we *know* we can detect duplicates, assume their presence
1516	hasDuplicate = !support.detectDuplicates;
1517	sortInput = !support.sortStable && results.slice( 0 );
1518	results.sort( sortOrder );
1519
1520	if ( hasDuplicate ) {
1521		while ( (elem = results[i++]) ) {
1522			if ( elem === results[ i ] ) {
1523				j = duplicates.push( i );
1524			}
1525		}
1526		while ( j-- ) {
1527			results.splice( duplicates[ j ], 1 );
1528		}
1529	}
1530
1531	// Clear input after sorting to release objects
1532	// See https://github.com/jquery/sizzle/pull/225
1533	sortInput = null;
1534
1535	return results;
1536};
1537
1538/**
1539 * Utility function for retrieving the text value of an array of DOM nodes
1540 * @param {Array|Element} elem
1541 */
1542getText = Sizzle.getText = function( elem ) {
1543	var node,
1544		ret = "",
1545		i = 0,
1546		nodeType = elem.nodeType;
1547
1548	if ( !nodeType ) {
1549		// If no nodeType, this is expected to be an array
1550		while ( (node = elem[i++]) ) {
1551			// Do not traverse comment nodes
1552			ret += getText( node );
1553		}
1554	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
1555		// Use textContent for elements
1556		// innerText usage removed for consistency of new lines (jQuery #11153)
1557		if ( typeof elem.textContent === "string" ) {
1558			return elem.textContent;
1559		} else {
1560			// Traverse its children
1561			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
1562				ret += getText( elem );
1563			}
1564		}
1565	} else if ( nodeType === 3 || nodeType === 4 ) {
1566		return elem.nodeValue;
1567	}
1568	// Do not include comment or processing instruction nodes
1569
1570	return ret;
1571};
1572
1573Expr = Sizzle.selectors = {
1574
1575	// Can be adjusted by the user
1576	cacheLength: 50,
1577
1578	createPseudo: markFunction,
1579
1580	match: matchExpr,
1581
1582	attrHandle: {},
1583
1584	find: {},
1585
1586	relative: {
1587		">": { dir: "parentNode", first: true },
1588		" ": { dir: "parentNode" },
1589		"+": { dir: "previousSibling", first: true },
1590		"~": { dir: "previousSibling" }
1591	},
1592
1593	preFilter: {
1594		"ATTR": function( match ) {
1595			match[1] = match[1].replace( runescape, funescape );
1596
1597			// Move the given value to match[3] whether quoted or unquoted
1598			match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
1599
1600			if ( match[2] === "~=" ) {
1601				match[3] = " " + match[3] + " ";
1602			}
1603
1604			return match.slice( 0, 4 );
1605		},
1606
1607		"CHILD": function( match ) {
1608			/* matches from matchExpr["CHILD"]
1609				1 type (only|nth|...)
1610				2 what (child|of-type)
1611				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
1612				4 xn-component of xn+y argument ([+-]?\d*n|)
1613				5 sign of xn-component
1614				6 x of xn-component
1615				7 sign of y-component
1616				8 y of y-component
1617			*/
1618			match[1] = match[1].toLowerCase();
1619
1620			if ( match[1].slice( 0, 3 ) === "nth" ) {
1621				// nth-* requires argument
1622				if ( !match[3] ) {
1623					Sizzle.error( match[0] );
1624				}
1625
1626				// numeric x and y parameters for Expr.filter.CHILD
1627				// remember that false/true cast respectively to 0/1
1628				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
1629				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
1630
1631			// other types prohibit arguments
1632			} else if ( match[3] ) {
1633				Sizzle.error( match[0] );
1634			}
1635
1636			return match;
1637		},
1638
1639		"PSEUDO": function( match ) {
1640			var excess,
1641				unquoted = !match[6] && match[2];
1642
1643			if ( matchExpr["CHILD"].test( match[0] ) ) {
1644				return null;
1645			}
1646
1647			// Accept quoted arguments as-is
1648			if ( match[3] ) {
1649				match[2] = match[4] || match[5] || "";
1650
1651			// Strip excess characters from unquoted arguments
1652			} else if ( unquoted && rpseudo.test( unquoted ) &&
1653				// Get excess from tokenize (recursively)
1654				(excess = tokenize( unquoted, true )) &&
1655				// advance to the next closing parenthesis
1656				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
1657
1658				// excess is a negative index
1659				match[0] = match[0].slice( 0, excess );
1660				match[2] = unquoted.slice( 0, excess );
1661			}
1662
1663			// Return only captures needed by the pseudo filter method (type and argument)
1664			return match.slice( 0, 3 );
1665		}
1666	},
1667
1668	filter: {
1669
1670		"TAG": function( nodeNameSelector ) {
1671			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
1672			return nodeNameSelector === "*" ?
1673				function() { return true; } :
1674				function( elem ) {
1675					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
1676				};
1677		},
1678
1679		"CLASS": function( className ) {
1680			var pattern = classCache[ className + " " ];
1681
1682			return pattern ||
1683				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
1684				classCache( className, function( elem ) {
1685					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
1686				});
1687		},
1688
1689		"ATTR": function( name, operator, check ) {
1690			return function( elem ) {
1691				var result = Sizzle.attr( elem, name );
1692
1693				if ( result == null ) {
1694					return operator === "!=";
1695				}
1696				if ( !operator ) {
1697					return true;
1698				}
1699
1700				result += "";
1701
1702				return operator === "=" ? result === check :
1703					operator === "!=" ? result !== check :
1704					operator === "^=" ? check && result.indexOf( check ) === 0 :
1705					operator === "*=" ? check && result.indexOf( check ) > -1 :
1706					operator === "$=" ? check && result.slice( -check.length ) === check :
1707					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
1708					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
1709					false;
1710			};
1711		},
1712
1713		"CHILD": function( type, what, argument, first, last ) {
1714			var simple = type.slice( 0, 3 ) !== "nth",
1715				forward = type.slice( -4 ) !== "last",
1716				ofType = what === "of-type";
1717
1718			return first === 1 && last === 0 ?
1719
1720				// Shortcut for :nth-*(n)
1721				function( elem ) {
1722					return !!elem.parentNode;
1723				} :
1724
1725				function( elem, context, xml ) {
1726					var cache, uniqueCache, outerCache, node, nodeIndex, start,
1727						dir = simple !== forward ? "nextSibling" : "previousSibling",
1728						parent = elem.parentNode,
1729						name = ofType && elem.nodeName.toLowerCase(),
1730						useCache = !xml && !ofType,
1731						diff = false;
1732
1733					if ( parent ) {
1734
1735						// :(first|last|only)-(child|of-type)
1736						if ( simple ) {
1737							while ( dir ) {
1738								node = elem;
1739								while ( (node = node[ dir ]) ) {
1740									if ( ofType ?
1741										node.nodeName.toLowerCase() === name :
1742										node.nodeType === 1 ) {
1743
1744										return false;
1745									}
1746								}
1747								// Reverse direction for :only-* (if we haven't yet done so)
1748								start = dir = type === "only" && !start && "nextSibling";
1749							}
1750							return true;
1751						}
1752
1753						start = [ forward ? parent.firstChild : parent.lastChild ];
1754
1755						// non-xml :nth-child(...) stores cache data on `parent`
1756						if ( forward && useCache ) {
1757
1758							// Seek `elem` from a previously-cached index
1759
1760							// ...in a gzip-friendly way
1761							node = parent;
1762							outerCache = node[ expando ] || (node[ expando ] = {});
1763
1764							// Support: IE <9 only
1765							// Defend against cloned attroperties (jQuery gh-1709)
1766							uniqueCache = outerCache[ node.uniqueID ] ||
1767								(outerCache[ node.uniqueID ] = {});
1768
1769							cache = uniqueCache[ type ] || [];
1770							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
1771							diff = nodeIndex && cache[ 2 ];
1772							node = nodeIndex && parent.childNodes[ nodeIndex ];
1773
1774							while ( (node = ++nodeIndex && node && node[ dir ] ||
1775
1776								// Fallback to seeking `elem` from the start
1777								(diff = nodeIndex = 0) || start.pop()) ) {
1778
1779								// When found, cache indexes on `parent` and break
1780								if ( node.nodeType === 1 && ++diff && node === elem ) {
1781									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
1782									break;
1783								}
1784							}
1785
1786						} else {
1787							// Use previously-cached element index if available
1788							if ( useCache ) {
1789								// ...in a gzip-friendly way
1790								node = elem;
1791								outerCache = node[ expando ] || (node[ expando ] = {});
1792
1793								// Support: IE <9 only
1794								// Defend against cloned attroperties (jQuery gh-1709)
1795								uniqueCache = outerCache[ node.uniqueID ] ||
1796									(outerCache[ node.uniqueID ] = {});
1797
1798								cache = uniqueCache[ type ] || [];
1799								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
1800								diff = nodeIndex;
1801							}
1802
1803							// xml :nth-child(...)
1804							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
1805							if ( diff === false ) {
1806								// Use the same loop as above to seek `elem` from the start
1807								while ( (node = ++nodeIndex && node && node[ dir ] ||
1808									(diff = nodeIndex = 0) || start.pop()) ) {
1809
1810									if ( ( ofType ?
1811										node.nodeName.toLowerCase() === name :
1812										node.nodeType === 1 ) &&
1813										++diff ) {
1814
1815										// Cache the index of each encountered element
1816										if ( useCache ) {
1817											outerCache = node[ expando ] || (node[ expando ] = {});
1818
1819											// Support: IE <9 only
1820											// Defend against cloned attroperties (jQuery gh-1709)
1821											uniqueCache = outerCache[ node.uniqueID ] ||
1822												(outerCache[ node.uniqueID ] = {});
1823
1824											uniqueCache[ type ] = [ dirruns, diff ];
1825										}
1826
1827										if ( node === elem ) {
1828											break;
1829										}
1830									}
1831								}
1832							}
1833						}
1834
1835						// Incorporate the offset, then check against cycle size
1836						diff -= last;
1837						return diff === first || ( diff % first === 0 && diff / first >= 0 );
1838					}
1839				};
1840		},
1841
1842		"PSEUDO": function( pseudo, argument ) {
1843			// pseudo-class names are case-insensitive
1844			// http://www.w3.org/TR/selectors/#pseudo-classes
1845			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
1846			// Remember that setFilters inherits from pseudos
1847			var args,
1848				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
1849					Sizzle.error( "unsupported pseudo: " + pseudo );
1850
1851			// The user may use createPseudo to indicate that
1852			// arguments are needed to create the filter function
1853			// just as Sizzle does
1854			if ( fn[ expando ] ) {
1855				return fn( argument );
1856			}
1857
1858			// But maintain support for old signatures
1859			if ( fn.length > 1 ) {
1860				args = [ pseudo, pseudo, "", argument ];
1861				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
1862					markFunction(function( seed, matches ) {
1863						var idx,
1864							matched = fn( seed, argument ),
1865							i = matched.length;
1866						while ( i-- ) {
1867							idx = indexOf( seed, matched[i] );
1868							seed[ idx ] = !( matches[ idx ] = matched[i] );
1869						}
1870					}) :
1871					function( elem ) {
1872						return fn( elem, 0, args );
1873					};
1874			}
1875
1876			return fn;
1877		}
1878	},
1879
1880	pseudos: {
1881		// Potentially complex pseudos
1882		"not": markFunction(function( selector ) {
1883			// Trim the selector passed to compile
1884			// to avoid treating leading and trailing
1885			// spaces as combinators
1886			var input = [],
1887				results = [],
1888				matcher = compile( selector.replace( rtrim, "$1" ) );
1889
1890			return matcher[ expando ] ?
1891				markFunction(function( seed, matches, context, xml ) {
1892					var elem,
1893						unmatched = matcher( seed, null, xml, [] ),
1894						i = seed.length;
1895
1896					// Match elements unmatched by `matcher`
1897					while ( i-- ) {
1898						if ( (elem = unmatched[i]) ) {
1899							seed[i] = !(matches[i] = elem);
1900						}
1901					}
1902				}) :
1903				function( elem, context, xml ) {
1904					input[0] = elem;
1905					matcher( input, null, xml, results );
1906					// Don't keep the element (issue #299)
1907					input[0] = null;
1908					return !results.pop();
1909				};
1910		}),
1911
1912		"has": markFunction(function( selector ) {
1913			return function( elem ) {
1914				return Sizzle( selector, elem ).length > 0;
1915			};
1916		}),
1917
1918		"contains": markFunction(function( text ) {
1919			text = text.replace( runescape, funescape );
1920			return function( elem ) {
1921				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
1922			};
1923		}),
1924
1925		// "Whether an element is represented by a :lang() selector
1926		// is based solely on the element's language value
1927		// being equal to the identifier C,
1928		// or beginning with the identifier C immediately followed by "-".
1929		// The matching of C against the element's language value is performed case-insensitively.
1930		// The identifier C does not have to be a valid language name."
1931		// http://www.w3.org/TR/selectors/#lang-pseudo
1932		"lang": markFunction( function( lang ) {
1933			// lang value must be a valid identifier
1934			if ( !ridentifier.test(lang || "") ) {
1935				Sizzle.error( "unsupported lang: " + lang );
1936			}
1937			lang = lang.replace( runescape, funescape ).toLowerCase();
1938			return function( elem ) {
1939				var elemLang;
1940				do {
1941					if ( (elemLang = documentIsHTML ?
1942						elem.lang :
1943						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
1944
1945						elemLang = elemLang.toLowerCase();
1946						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
1947					}
1948				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
1949				return false;
1950			};
1951		}),
1952
1953		// Miscellaneous
1954		"target": function( elem ) {
1955			var hash = window.location && window.location.hash;
1956			return hash && hash.slice( 1 ) === elem.id;
1957		},
1958
1959		"root": function( elem ) {
1960			return elem === docElem;
1961		},
1962
1963		"focus": function( elem ) {
1964			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
1965		},
1966
1967		// Boolean properties
1968		"enabled": function( elem ) {
1969			return elem.disabled === false;
1970		},
1971
1972		"disabled": function( elem ) {
1973			return elem.disabled === true;
1974		},
1975
1976		"checked": function( elem ) {
1977			// In CSS3, :checked should return both checked and selected elements
1978			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
1979			var nodeName = elem.nodeName.toLowerCase();
1980			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
1981		},
1982
1983		"selected": function( elem ) {
1984			// Accessing this property makes selected-by-default
1985			// options in Safari work properly
1986			if ( elem.parentNode ) {
1987				elem.parentNode.selectedIndex;
1988			}
1989
1990			return elem.selected === true;
1991		},
1992
1993		// Contents
1994		"empty": function( elem ) {
1995			// http://www.w3.org/TR/selectors/#empty-pseudo
1996			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
1997			//   but not by others (comment: 8; processing instruction: 7; etc.)
1998			// nodeType < 6 works because attributes (2) do not appear as children
1999			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
2000				if ( elem.nodeType < 6 ) {
2001					return false;
2002				}
2003			}
2004			return true;
2005		},
2006
2007		"parent": function( elem ) {
2008			return !Expr.pseudos["empty"]( elem );
2009		},
2010
2011		// Element/input types
2012		"header": function( elem ) {
2013			return rheader.test( elem.nodeName );
2014		},
2015
2016		"input": function( elem ) {
2017			return rinputs.test( elem.nodeName );
2018		},
2019
2020		"button": function( elem ) {
2021			var name = elem.nodeName.toLowerCase();
2022			return name === "input" && elem.type === "button" || name === "button";
2023		},
2024
2025		"text": function( elem ) {
2026			var attr;
2027			return elem.nodeName.toLowerCase() === "input" &&
2028				elem.type === "text" &&
2029
2030				// Support: IE<8
2031				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
2032				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
2033		},
2034
2035		// Position-in-collection
2036		"first": createPositionalPseudo(function() {
2037			return [ 0 ];
2038		}),
2039
2040		"last": createPositionalPseudo(function( matchIndexes, length ) {
2041			return [ length - 1 ];
2042		}),
2043
2044		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
2045			return [ argument < 0 ? argument + length : argument ];
2046		}),
2047
2048		"even": createPositionalPseudo(function( matchIndexes, length ) {
2049			var i = 0;
2050			for ( ; i < length; i += 2 ) {
2051				matchIndexes.push( i );
2052			}
2053			return matchIndexes;
2054		}),
2055
2056		"odd": createPositionalPseudo(function( matchIndexes, length ) {
2057			var i = 1;
2058			for ( ; i < length; i += 2 ) {
2059				matchIndexes.push( i );
2060			}
2061			return matchIndexes;
2062		}),
2063
2064		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
2065			var i = argument < 0 ? argument + length : argument;
2066			for ( ; --i >= 0; ) {
2067				matchIndexes.push( i );
2068			}
2069			return matchIndexes;
2070		}),
2071
2072		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
2073			var i = argument < 0 ? argument + length : argument;
2074			for ( ; ++i < length; ) {
2075				matchIndexes.push( i );
2076			}
2077			return matchIndexes;
2078		})
2079	}
2080};
2081
2082Expr.pseudos["nth"] = Expr.pseudos["eq"];
2083
2084// Add button/input type pseudos
2085for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
2086	Expr.pseudos[ i ] = createInputPseudo( i );
2087}
2088for ( i in { submit: true, reset: true } ) {
2089	Expr.pseudos[ i ] = createButtonPseudo( i );
2090}
2091
2092// Easy API for creating new setFilters
2093function setFilters() {}
2094setFilters.prototype = Expr.filters = Expr.pseudos;
2095Expr.setFilters = new setFilters();
2096
2097tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
2098	var matched, match, tokens, type,
2099		soFar, groups, preFilters,
2100		cached = tokenCache[ selector + " " ];
2101
2102	if ( cached ) {
2103		return parseOnly ? 0 : cached.slice( 0 );
2104	}
2105
2106	soFar = selector;
2107	groups = [];
2108	preFilters = Expr.preFilter;
2109
2110	while ( soFar ) {
2111
2112		// Comma and first run
2113		if ( !matched || (match = rcomma.exec( soFar )) ) {
2114			if ( match ) {
2115				// Don't consume trailing commas as valid
2116				soFar = soFar.slice( match[0].length ) || soFar;
2117			}
2118			groups.push( (tokens = []) );
2119		}
2120
2121		matched = false;
2122
2123		// Combinators
2124		if ( (match = rcombinators.exec( soFar )) ) {
2125			matched = match.shift();
2126			tokens.push({
2127				value: matched,
2128				// Cast descendant combinators to space
2129				type: match[0].replace( rtrim, " " )
2130			});
2131			soFar = soFar.slice( matched.length );
2132		}
2133
2134		// Filters
2135		for ( type in Expr.filter ) {
2136			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
2137				(match = preFilters[ type ]( match ))) ) {
2138				matched = match.shift();
2139				tokens.push({
2140					value: matched,
2141					type: type,
2142					matches: match
2143				});
2144				soFar = soFar.slice( matched.length );
2145			}
2146		}
2147
2148		if ( !matched ) {
2149			break;
2150		}
2151	}
2152
2153	// Return the length of the invalid excess
2154	// if we're just parsing
2155	// Otherwise, throw an error or return tokens
2156	return parseOnly ?
2157		soFar.length :
2158		soFar ?
2159			Sizzle.error( selector ) :
2160			// Cache the tokens
2161			tokenCache( selector, groups ).slice( 0 );
2162};
2163
2164function toSelector( tokens ) {
2165	var i = 0,
2166		len = tokens.length,
2167		selector = "";
2168	for ( ; i < len; i++ ) {
2169		selector += tokens[i].value;
2170	}
2171	return selector;
2172}
2173
2174function addCombinator( matcher, combinator, base ) {
2175	var dir = combinator.dir,
2176		checkNonElements = base && dir === "parentNode",
2177		doneName = done++;
2178
2179	return combinator.first ?
2180		// Check against closest ancestor/preceding element
2181		function( elem, context, xml ) {
2182			while ( (elem = elem[ dir ]) ) {
2183				if ( elem.nodeType === 1 || checkNonElements ) {
2184					return matcher( elem, context, xml );
2185				}
2186			}
2187		} :
2188
2189		// Check against all ancestor/preceding elements
2190		function( elem, context, xml ) {
2191			var oldCache, uniqueCache, outerCache,
2192				newCache = [ dirruns, doneName ];
2193
2194			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
2195			if ( xml ) {
2196				while ( (elem = elem[ dir ]) ) {
2197					if ( elem.nodeType === 1 || checkNonElements ) {
2198						if ( matcher( elem, context, xml ) ) {
2199							return true;
2200						}
2201					}
2202				}
2203			} else {
2204				while ( (elem = elem[ dir ]) ) {
2205					if ( elem.nodeType === 1 || checkNonElements ) {
2206						outerCache = elem[ expando ] || (elem[ expando ] = {});
2207
2208						// Support: IE <9 only
2209						// Defend against cloned attroperties (jQuery gh-1709)
2210						uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
2211
2212						if ( (oldCache = uniqueCache[ dir ]) &&
2213							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
2214
2215							// Assign to newCache so results back-propagate to previous elements
2216							return (newCache[ 2 ] = oldCache[ 2 ]);
2217						} else {
2218							// Reuse newcache so results back-propagate to previous elements
2219							uniqueCache[ dir ] = newCache;
2220
2221							// A match means we're done; a fail means we have to keep checking
2222							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
2223								return true;
2224							}
2225						}
2226					}
2227				}
2228			}
2229		};
2230}
2231
2232function elementMatcher( matchers ) {
2233	return matchers.length > 1 ?
2234		function( elem, context, xml ) {
2235			var i = matchers.length;
2236			while ( i-- ) {
2237				if ( !matchers[i]( elem, context, xml ) ) {
2238					return false;
2239				}
2240			}
2241			return true;
2242		} :
2243		matchers[0];
2244}
2245
2246function multipleContexts( selector, contexts, results ) {
2247	var i = 0,
2248		len = contexts.length;
2249	for ( ; i < len; i++ ) {
2250		Sizzle( selector, contexts[i], results );
2251	}
2252	return results;
2253}
2254
2255function condense( unmatched, map, filter, context, xml ) {
2256	var elem,
2257		newUnmatched = [],
2258		i = 0,
2259		len = unmatched.length,
2260		mapped = map != null;
2261
2262	for ( ; i < len; i++ ) {
2263		if ( (elem = unmatched[i]) ) {
2264			if ( !filter || filter( elem, context, xml ) ) {
2265				newUnmatched.push( elem );
2266				if ( mapped ) {
2267					map.push( i );
2268				}
2269			}
2270		}
2271	}
2272
2273	return newUnmatched;
2274}
2275
2276function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
2277	if ( postFilter && !postFilter[ expando ] ) {
2278		postFilter = setMatcher( postFilter );
2279	}
2280	if ( postFinder && !postFinder[ expando ] ) {
2281		postFinder = setMatcher( postFinder, postSelector );
2282	}
2283	return markFunction(function( seed, results, context, xml ) {
2284		var temp, i, elem,
2285			preMap = [],
2286			postMap = [],
2287			preexisting = results.length,
2288
2289			// Get initial elements from seed or context
2290			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
2291
2292			// Prefilter to get matcher input, preserving a map for seed-results synchronization
2293			matcherIn = preFilter && ( seed || !selector ) ?
2294				condense( elems, preMap, preFilter, context, xml ) :
2295				elems,
2296
2297			matcherOut = matcher ?
2298				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
2299				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
2300
2301					// ...intermediate processing is necessary
2302					[] :
2303
2304					// ...otherwise use results directly
2305					results :
2306				matcherIn;
2307
2308		// Find primary matches
2309		if ( matcher ) {
2310			matcher( matcherIn, matcherOut, context, xml );
2311		}
2312
2313		// Apply postFilter
2314		if ( postFilter ) {
2315			temp = condense( matcherOut, postMap );
2316			postFilter( temp, [], context, xml );
2317
2318			// Un-match failing elements by moving them back to matcherIn
2319			i = temp.length;
2320			while ( i-- ) {
2321				if ( (elem = temp[i]) ) {
2322					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
2323				}
2324			}
2325		}
2326
2327		if ( seed ) {
2328			if ( postFinder || preFilter ) {
2329				if ( postFinder ) {
2330					// Get the final matcherOut by condensing this intermediate into postFinder contexts
2331					temp = [];
2332					i = matcherOut.length;
2333					while ( i-- ) {
2334						if ( (elem = matcherOut[i]) ) {
2335							// Restore matcherIn since elem is not yet a final match
2336							temp.push( (matcherIn[i] = elem) );
2337						}
2338					}
2339					postFinder( null, (matcherOut = []), temp, xml );
2340				}
2341
2342				// Move matched elements from seed to results to keep them synchronized
2343				i = matcherOut.length;
2344				while ( i-- ) {
2345					if ( (elem = matcherOut[i]) &&
2346						(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
2347
2348						seed[temp] = !(results[temp] = elem);
2349					}
2350				}
2351			}
2352
2353		// Add elements to results, through postFinder if defined
2354		} else {
2355			matcherOut = condense(
2356				matcherOut === results ?
2357					matcherOut.splice( preexisting, matcherOut.length ) :
2358					matcherOut
2359			);
2360			if ( postFinder ) {
2361				postFinder( null, results, matcherOut, xml );
2362			} else {
2363				push.apply( results, matcherOut );
2364			}
2365		}
2366	});
2367}
2368
2369function matcherFromTokens( tokens ) {
2370	var checkContext, matcher, j,
2371		len = tokens.length,
2372		leadingRelative = Expr.relative[ tokens[0].type ],
2373		implicitRelative = leadingRelative || Expr.relative[" "],
2374		i = leadingRelative ? 1 : 0,
2375
2376		// The foundational matcher ensures that elements are reachable from top-level context(s)
2377		matchContext = addCombinator( function( elem ) {
2378			return elem === checkContext;
2379		}, implicitRelative, true ),
2380		matchAnyContext = addCombinator( function( elem ) {
2381			return indexOf( checkContext, elem ) > -1;
2382		}, implicitRelative, true ),
2383		matchers = [ function( elem, context, xml ) {
2384			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
2385				(checkContext = context).nodeType ?
2386					matchContext( elem, context, xml ) :
2387					matchAnyContext( elem, context, xml ) );
2388			// Avoid hanging onto element (issue #299)
2389			checkContext = null;
2390			return ret;
2391		} ];
2392
2393	for ( ; i < len; i++ ) {
2394		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
2395			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
2396		} else {
2397			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
2398
2399			// Return special upon seeing a positional matcher
2400			if ( matcher[ expando ] ) {
2401				// Find the next relative operator (if any) for proper handling
2402				j = ++i;
2403				for ( ; j < len; j++ ) {
2404					if ( Expr.relative[ tokens[j].type ] ) {
2405						break;
2406					}
2407				}
2408				return setMatcher(
2409					i > 1 && elementMatcher( matchers ),
2410					i > 1 && toSelector(
2411						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
2412						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
2413					).replace( rtrim, "$1" ),
2414					matcher,
2415					i < j && matcherFromTokens( tokens.slice( i, j ) ),
2416					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
2417					j < len && toSelector( tokens )
2418				);
2419			}
2420			matchers.push( matcher );
2421		}
2422	}
2423
2424	return elementMatcher( matchers );
2425}
2426
2427function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
2428	var bySet = setMatchers.length > 0,
2429		byElement = elementMatchers.length > 0,
2430		superMatcher = function( seed, context, xml, results, outermost ) {
2431			var elem, j, matcher,
2432				matchedCount = 0,
2433				i = "0",
2434				unmatched = seed && [],
2435				setMatched = [],
2436				contextBackup = outermostContext,
2437				// We must always have either seed elements or outermost context
2438				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
2439				// Use integer dirruns iff this is the outermost matcher
2440				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
2441				len = elems.length;
2442
2443			if ( outermost ) {
2444				outermostContext = context === document || context || outermost;
2445			}
2446
2447			// Add elements passing elementMatchers directly to results
2448			// Support: IE<9, Safari
2449			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
2450			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
2451				if ( byElement && elem ) {
2452					j = 0;
2453					if ( !context && elem.ownerDocument !== document ) {
2454						setDocument( elem );
2455						xml = !documentIsHTML;
2456					}
2457					while ( (matcher = elementMatchers[j++]) ) {
2458						if ( matcher( elem, context || document, xml) ) {
2459							results.push( elem );
2460							break;
2461						}
2462					}
2463					if ( outermost ) {
2464						dirruns = dirrunsUnique;
2465					}
2466				}
2467
2468				// Track unmatched elements for set filters
2469				if ( bySet ) {
2470					// They will have gone through all possible matchers
2471					if ( (elem = !matcher && elem) ) {
2472						matchedCount--;
2473					}
2474
2475					// Lengthen the array for every element, matched or not
2476					if ( seed ) {
2477						unmatched.push( elem );
2478					}
2479				}
2480			}
2481
2482			// `i` is now the count of elements visited above, and adding it to `matchedCount`
2483			// makes the latter nonnegative.
2484			matchedCount += i;
2485
2486			// Apply set filters to unmatched elements
2487			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
2488			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
2489			// no element matchers and no seed.
2490			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
2491			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
2492			// numerically zero.
2493			if ( bySet && i !== matchedCount ) {
2494				j = 0;
2495				while ( (matcher = setMatchers[j++]) ) {
2496					matcher( unmatched, setMatched, context, xml );
2497				}
2498
2499				if ( seed ) {
2500					// Reintegrate element matches to eliminate the need for sorting
2501					if ( matchedCount > 0 ) {
2502						while ( i-- ) {
2503							if ( !(unmatched[i] || setMatched[i]) ) {
2504								setMatched[i] = pop.call( results );
2505							}
2506						}
2507					}
2508
2509					// Discard index placeholder values to get only actual matches
2510					setMatched = condense( setMatched );
2511				}
2512
2513				// Add matches to results
2514				push.apply( results, setMatched );
2515
2516				// Seedless set matches succeeding multiple successful matchers stipulate sorting
2517				if ( outermost && !seed && setMatched.length > 0 &&
2518					( matchedCount + setMatchers.length ) > 1 ) {
2519
2520					Sizzle.uniqueSort( results );
2521				}
2522			}
2523
2524			// Override manipulation of globals by nested matchers
2525			if ( outermost ) {
2526				dirruns = dirrunsUnique;
2527				outermostContext = contextBackup;
2528			}
2529
2530			return unmatched;
2531		};
2532
2533	return bySet ?
2534		markFunction( superMatcher ) :
2535		superMatcher;
2536}
2537
2538compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
2539	var i,
2540		setMatchers = [],
2541		elementMatchers = [],
2542		cached = compilerCache[ selector + " " ];
2543
2544	if ( !cached ) {
2545		// Generate a function of recursive functions that can be used to check each element
2546		if ( !match ) {
2547			match = tokenize( selector );
2548		}
2549		i = match.length;
2550		while ( i-- ) {
2551			cached = matcherFromTokens( match[i] );
2552			if ( cached[ expando ] ) {
2553				setMatchers.push( cached );
2554			} else {
2555				elementMatchers.push( cached );
2556			}
2557		}
2558
2559		// Cache the compiled function
2560		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
2561
2562		// Save selector and tokenization
2563		cached.selector = selector;
2564	}
2565	return cached;
2566};
2567
2568/**
2569 * A low-level selection function that works with Sizzle's compiled
2570 *  selector functions
2571 * @param {String|Function} selector A selector or a pre-compiled
2572 *  selector function built with Sizzle.compile
2573 * @param {Element} context
2574 * @param {Array} [results]
2575 * @param {Array} [seed] A set of elements to match against
2576 */
2577select = Sizzle.select = function( selector, context, results, seed ) {
2578	var i, tokens, token, type, find,
2579		compiled = typeof selector === "function" && selector,
2580		match = !seed && tokenize( (selector = compiled.selector || selector) );
2581
2582	results = results || [];
2583
2584	// Try to minimize operations if there is only one selector in the list and no seed
2585	// (the latter of which guarantees us context)
2586	if ( match.length === 1 ) {
2587
2588		// Reduce context if the leading compound selector is an ID
2589		tokens = match[0] = match[0].slice( 0 );
2590		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
2591				support.getById && context.nodeType === 9 && documentIsHTML &&
2592				Expr.relative[ tokens[1].type ] ) {
2593
2594			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
2595			if ( !context ) {
2596				return results;
2597
2598			// Precompiled matchers will still verify ancestry, so step up a level
2599			} else if ( compiled ) {
2600				context = context.parentNode;
2601			}
2602
2603			selector = selector.slice( tokens.shift().value.length );
2604		}
2605
2606		// Fetch a seed set for right-to-left matching
2607		i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
2608		while ( i-- ) {
2609			token = tokens[i];
2610
2611			// Abort if we hit a combinator
2612			if ( Expr.relative[ (type = token.type) ] ) {
2613				break;
2614			}
2615			if ( (find = Expr.find[ type ]) ) {
2616				// Search, expanding context for leading sibling combinators
2617				if ( (seed = find(
2618					token.matches[0].replace( runescape, funescape ),
2619					rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
2620				)) ) {
2621
2622					// If seed is empty or no tokens remain, we can return early
2623					tokens.splice( i, 1 );
2624					selector = seed.length && toSelector( tokens );
2625					if ( !selector ) {
2626						push.apply( results, seed );
2627						return results;
2628					}
2629
2630					break;
2631				}
2632			}
2633		}
2634	}
2635
2636	// Compile and execute a filtering function if one is not provided
2637	// Provide `match` to avoid retokenization if we modified the selector above
2638	( compiled || compile( selector, match ) )(
2639		seed,
2640		context,
2641		!documentIsHTML,
2642		results,
2643		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
2644	);
2645	return results;
2646};
2647
2648// One-time assignments
2649
2650// Sort stability
2651support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
2652
2653// Support: Chrome 14-35+
2654// Always assume duplicates if they aren't passed to the comparison function
2655support.detectDuplicates = !!hasDuplicate;
2656
2657// Initialize against the default document
2658setDocument();
2659
2660// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
2661// Detached nodes confoundingly follow *each other*
2662support.sortDetached = assert(function( div1 ) {
2663	// Should return 1, but returns 4 (following)
2664	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
2665});
2666
2667// Support: IE<8
2668// Prevent attribute/property "interpolation"
2669// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
2670if ( !assert(function( div ) {
2671	div.innerHTML = "<a href='#'></a>";
2672	return div.firstChild.getAttribute("href") === "#" ;
2673}) ) {
2674	addHandle( "type|href|height|width", function( elem, name, isXML ) {
2675		if ( !isXML ) {
2676			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
2677		}
2678	});
2679}
2680
2681// Support: IE<9
2682// Use defaultValue in place of getAttribute("value")
2683if ( !support.attributes || !assert(function( div ) {
2684	div.innerHTML = "<input/>";
2685	div.firstChild.setAttribute( "value", "" );
2686	return div.firstChild.getAttribute( "value" ) === "";
2687}) ) {
2688	addHandle( "value", function( elem, name, isXML ) {
2689		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
2690			return elem.defaultValue;
2691		}
2692	});
2693}
2694
2695// Support: IE<9
2696// Use getAttributeNode to fetch booleans when getAttribute lies
2697if ( !assert(function( div ) {
2698	return div.getAttribute("disabled") == null;
2699}) ) {
2700	addHandle( booleans, function( elem, name, isXML ) {
2701		var val;
2702		if ( !isXML ) {
2703			return elem[ name ] === true ? name.toLowerCase() :
2704					(val = elem.getAttributeNode( name )) && val.specified ?
2705					val.value :
2706				null;
2707		}
2708	});
2709}
2710
2711return Sizzle;
2712
2713})( window );
2714
2715
2716
2717jQuery.find = Sizzle;
2718jQuery.expr = Sizzle.selectors;
2719jQuery.expr[ ":" ] = jQuery.expr.pseudos;
2720jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
2721jQuery.text = Sizzle.getText;
2722jQuery.isXMLDoc = Sizzle.isXML;
2723jQuery.contains = Sizzle.contains;
2724
2725
2726
2727var dir = function( elem, dir, until ) {
2728	var matched = [],
2729		truncate = until !== undefined;
2730
2731	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
2732		if ( elem.nodeType === 1 ) {
2733			if ( truncate && jQuery( elem ).is( until ) ) {
2734				break;
2735			}
2736			matched.push( elem );
2737		}
2738	}
2739	return matched;
2740};
2741
2742
2743var siblings = function( n, elem ) {
2744	var matched = [];
2745
2746	for ( ; n; n = n.nextSibling ) {
2747		if ( n.nodeType === 1 && n !== elem ) {
2748			matched.push( n );
2749		}
2750	}
2751
2752	return matched;
2753};
2754
2755
2756var rneedsContext = jQuery.expr.match.needsContext;
2757
2758var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ );
2759
2760
2761
2762var risSimple = /^.[^:#\[\.,]*$/;
2763
2764// Implement the identical functionality for filter and not
2765function winnow( elements, qualifier, not ) {
2766	if ( jQuery.isFunction( qualifier ) ) {
2767		return jQuery.grep( elements, function( elem, i ) {
2768			/* jshint -W018 */
2769			return !!qualifier.call( elem, i, elem ) !== not;
2770		} );
2771
2772	}
2773
2774	if ( qualifier.nodeType ) {
2775		return jQuery.grep( elements, function( elem ) {
2776			return ( elem === qualifier ) !== not;
2777		} );
2778
2779	}
2780
2781	if ( typeof qualifier === "string" ) {
2782		if ( risSimple.test( qualifier ) ) {
2783			return jQuery.filter( qualifier, elements, not );
2784		}
2785
2786		qualifier = jQuery.filter( qualifier, elements );
2787	}
2788
2789	return jQuery.grep( elements, function( elem ) {
2790		return ( 
2790jQuery.inArray( elem, qualifier ) > -1 ) !== not;
2791	} );
2792}
2793
2794jQuery.filter = function( expr, elems, not ) {
2795	var elem = elems[ 0 ];
2796
2797	if ( not ) {
2798		expr = ":not(" + expr + ")";
2799	}
2800
2801	return elems.length === 1 && elem.nodeType === 1 ?
2802		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
2803		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
2804			return elem.nodeType === 1;
2805		} ) );
2806};
2807
2808jQuery.fn.extend( {
2809	find: function( selector ) {
2810		var i,
2811			ret = [],
2812			self = this,
2813			len = self.length;
2814
2815		if ( typeof selector !== "string" ) {
vendor: 5,394 bytes, lines 2816-3018
2816			return this.pushStack( jQuery( selector ).filter( function() {
2817				for ( i = 0; i < len; i++ ) {
2818					if ( jQuery.contains( self[ i ], this ) ) {
2819						return true;
2820					}
2821				}
2822			} ) );
2823		}
2824
2825		for ( i = 0; i < len; i++ ) {
2826			jQuery.find( selector, self[ i ], ret );
2827		}
2828
2829		// Needed because $( selector, context ) becomes $( context ).find( selector )
2830		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
2831		ret.selector = this.selector ? this.selector + " " + selector : selector;
2832		return ret;
2833	},
2834	filter: function( selector ) {
2835		return this.pushStack( winnow( this, selector || [], false ) );
2836	},
2837	not: function( selector ) {
2838		return this.pushStack( winnow( this, selector || [], true ) );
2839	},
2840	is: function( selector ) {
2841		return !!winnow(
2842			this,
2843
2844			// If this is a positional/relative selector, check membership in the returned set
2845			// so $("p:first").is("p:last") won't return true for a doc with two "p".
2846			typeof selector === "string" && rneedsContext.test( selector ) ?
2847				jQuery( selector ) :
2848				selector || [],
2849			false
2850		).length;
2851	}
2852} );
2853
2854
2855// Initialize a jQuery object
2856
2857
2858// A central reference to the root jQuery(document)
2859var rootjQuery,
2860
2861	// A simple way to check for HTML strings
2862	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
2863	// Strict HTML recognition (#11290: must start with <)
2864	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
2865
2866	init = jQuery.fn.init = function( selector, context, root ) {
2867		var match, elem;
2868
2869		// HANDLE: $(""), $(null), $(undefined), $(false)
2870		if ( !selector ) {
2871			return this;
2872		}
2873
2874		// init accepts an alternate rootjQuery
2875		// so migrate can support jQuery.sub (gh-2101)
2876		root = root || rootjQuery;
2877
2878		// Handle HTML strings
2879		if ( typeof selector === "string" ) {
2880			if ( selector.charAt( 0 ) === "<" &&
2881				selector.charAt( selector.length - 1 ) === ">" &&
2882				selector.length >= 3 ) {
2883
2884				// Assume that strings that start and end with <> are HTML and skip the regex check
2885				match = [ null, selector, null ];
2886
2887			} else {
2888				match = rquickExpr.exec( selector );
2889			}
2890
2891			// Match html or make sure no context is specified for #id
2892			if ( match && ( match[ 1 ] || !context ) ) {
2893
2894				// HANDLE: $(html) -> $(array)
2895				if ( match[ 1 ] ) {
2896					context = context instanceof jQuery ? context[ 0 ] : context;
2897
2898					// scripts is true for back-compat
2899					// Intentionally let the error be thrown if parseHTML is not present
2900					jQuery.merge( this, jQuery.parseHTML(
2901						match[ 1 ],
2902						context && context.nodeType ? context.ownerDocument || context : document,
2903						true
2904					) );
2905
2906					// HANDLE: $(html, props)
2907					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
2908						for ( match in context ) {
2909
2910							// Properties of context are called as methods if possible
2911							if ( jQuery.isFunction( this[ match ] ) ) {
2912								this[ match ]( context[ match ] );
2913
2914							// ...and otherwise set as attributes
2915							} else {
2916								this.attr( match, context[ match ] );
2917							}
2918						}
2919					}
2920
2921					return this;
2922
2923				// HANDLE: $(#id)
2924				} else {
2925					elem = document.getElementById( match[ 2 ] );
2926
2927					// Check parentNode to catch when Blackberry 4.6 returns
2928					// nodes that are no longer in the document #6963
2929					if ( elem && elem.parentNode ) {
2930
2931						// Handle the case where IE and Opera return items
2932						// by name instead of ID
2933						if ( elem.id !== match[ 2 ] ) {
2934							return rootjQuery.find( selector );
2935						}
2936
2937						// Otherwise, we inject the element directly into the jQuery object
2938						this.length = 1;
2939						this[ 0 ] = elem;
2940					}
2941
2942					this.context = document;
2943					this.selector = selector;
2944					return this;
2945				}
2946
2947			// HANDLE: $(expr, $(...))
2948			} else if ( !context || context.jquery ) {
2949				return ( context || root ).find( selector );
2950
2951			// HANDLE: $(expr, context)
2952			// (which is just equivalent to: $(context).find(expr)
2953			} else {
2954				return this.constructor( context ).find( selector );
2955			}
2956
2957		// HANDLE: $(DOMElement)
2958		} else if ( selector.nodeType ) {
2959			this.context = this[ 0 ] = selector;
2960			this.length = 1;
2961			return this;
2962
2963		// HANDLE: $(function)
2964		// Shortcut for document ready
2965		} else if ( jQuery.isFunction( selector ) ) {
2966			return typeof root.ready !== "undefined" ?
2967				root.ready( selector ) :
2968
2969				// Execute immediately if ready is not present
2970				selector( jQuery );
2971		}
2972
2973		if ( selector.selector !== undefined ) {
2974			this.selector = selector.selector;
2975			this.context = selector.context;
2976		}
2977
2978		return jQuery.makeArray( selector, this );
2979	};
2980
2981// Give the init function the jQuery prototype for later instantiation
2982init.prototype = jQuery.fn;
2983
2984// Initialize central reference
2985rootjQuery = jQuery( document );
2986
2987
2988var rparentsprev = /^(?:parents|prev(?:Until|All))/,
2989
2990	// methods guaranteed to produce a unique set when starting from a unique set
2991	guaranteedUnique = {
2992		children: true,
2993		contents: true,
2994		next: true,
2995		prev: true
2996	};
2997
2998jQuery.fn.extend( {
2999	has: function( target ) {
3000		var i,
3001			targets = jQuery( target, this ),
3002			len = targets.length;
3003
3004		return this.filter( function() {
3005			for ( i = 0; i < len; i++ ) {
3006				if ( jQuery.contains( this, targets[ i ] ) ) {
3007					return true;
3008				}
3009			}
3010		} );
3011	},
3012
3013	closest: function( selectors, context ) {
3014		var cur,
3015			i = 0,
3016			l = this.length,
3017			matched = [],
3018			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
vendor: 12,881 bytes, lines 3019-3527
3019				jQuery( selectors, context || this.context ) :
3020				0;
3021
3022		for ( ; i < l; i++ ) {
3023			for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
3024
3025				// Always skip document fragments
3026				if ( cur.nodeType < 11 && ( pos ?
3027					pos.index( cur ) > -1 :
3028
3029					// Don't pass non-elements to Sizzle
3030					cur.nodeType === 1 &&
3031						jQuery.find.matchesSelector( cur, selectors ) ) ) {
3032
3033					matched.push( cur );
3034					break;
3035				}
3036			}
3037		}
3038
3039		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
3040	},
3041
3042	// Determine the position of an element within
3043	// the matched set of elements
3044	index: function( elem ) {
3045
3046		// No argument, return index in parent
3047		if ( !elem ) {
3048			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
3049		}
3050
3051		// index in selector
3052		if ( typeof elem === "string" ) {
3053			return jQuery.inArray( this[ 0 ], jQuery( elem ) );
3054		}
3055
3056		// Locate the position of the desired element
3057		return jQuery.inArray(
3058
3059			// If it receives a jQuery object, the first element is used
3060			elem.jquery ? elem[ 0 ] : elem, this );
3061	},
3062
3063	add: function( selector, context ) {
3064		return this.pushStack(
3065			jQuery.uniqueSort(
3066				jQuery.merge( this.get(), jQuery( selector, context ) )
3067			)
3068		);
3069	},
3070
3071	addBack: function( selector ) {
3072		return this.add( selector == null ?
3073			this.prevObject : this.prevObject.filter( selector )
3074		);
3075	}
3076} );
3077
3078function sibling( cur, dir ) {
3079	do {
3080		cur = cur[ dir ];
3081	} while ( cur && cur.nodeType !== 1 );
3082
3083	return cur;
3084}
3085
3086jQuery.each( {
3087	parent: function( elem ) {
3088		var parent = elem.parentNode;
3089		return parent && parent.nodeType !== 11 ? parent : null;
3090	},
3091	parents: function( elem ) {
3092		return dir( elem, "parentNode" );
3093	},
3094	parentsUntil: function( elem, i, until ) {
3095		return dir( elem, "parentNode", until );
3096	},
3097	next: function( elem ) {
3098		return sibling( elem, "nextSibling" );
3099	},
3100	prev: function( elem ) {
3101		return sibling( elem, "previousSibling" );
3102	},
3103	nextAll: function( elem ) {
3104		return dir( elem, "nextSibling" );
3105	},
3106	prevAll: function( elem ) {
3107		return dir( elem, "previousSibling" );
3108	},
3109	nextUntil: function( elem, i, until ) {
3110		return dir( elem, "nextSibling", until );
3111	},
3112	prevUntil: function( elem, i, until ) {
3113		return dir( elem, "previousSibling", until );
3114	},
3115	siblings: function( elem ) {
3116		return siblings( ( elem.parentNode || {} ).firstChild, elem );
3117	},
3118	children: function( elem ) {
3119		return siblings( elem.firstChild );
3120	},
3121	contents: function( elem ) {
3122		return jQuery.nodeName( elem, "iframe" ) ?
3123			elem.contentDocument || elem.contentWindow.document :
3124			jQuery.merge( [], elem.childNodes );
3125	}
3126}, function( name, fn ) {
3127	jQuery.fn[ name ] = function( until, selector ) {
3128		var ret = jQuery.map( this, fn, until );
3129
3130		if ( name.slice( -5 ) !== "Until" ) {
3131			selector = until;
3132		}
3133
3134		if ( selector && typeof selector === "string" ) {
3135			ret = jQuery.filter( selector, ret );
3136		}
3137
3138		if ( this.length > 1 ) {
3139
3140			// Remove duplicates
3141			if ( !guaranteedUnique[ name ] ) {
3142				ret = jQuery.uniqueSort( ret );
3143			}
3144
3145			// Reverse order for parents* and prev-derivatives
3146			if ( rparentsprev.test( name ) ) {
3147				ret = ret.reverse();
3148			}
3149		}
3150
3151		return this.pushStack( ret );
3152	};
3153} );
3154var rnotwhite = ( /\S+/g );
3155
3156
3157
3158// Convert String-formatted options into Object-formatted ones
3159function createOptions( options ) {
3160	var object = {};
3161	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
3162		object[ flag ] = true;
3163	} );
3164	return object;
3165}
3166
3167/*
3168 * Create a callback list using the following parameters:
3169 *
3170 *	options: an optional list of space-separated options that will change how
3171 *			the callback list behaves or a more traditional option object
3172 *
3173 * By default a callback list will act like an event callback list and can be
3174 * "fired" multiple times.
3175 *
3176 * Possible options:
3177 *
3178 *	once:			will ensure the callback list can only be fired once (like a Deferred)
3179 *
3180 *	memory:			will keep track of previous values and will call any callback added
3181 *					after the list has been fired right away with the latest "memorized"
3182 *					values (like a Deferred)
3183 *
3184 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
3185 *
3186 *	stopOnFalse:	interrupt callings when a callback returns false
3187 *
3188 */
3189jQuery.Callbacks = function( options ) {
3190
3191	// Convert options from String-formatted to Object-formatted if needed
3192	// (we check in cache first)
3193	options = typeof options === "string" ?
3194		createOptions( options ) :
3195		jQuery.extend( {}, options );
3196
3197	var // Flag to know if list is currently firing
3198		firing,
3199
3200		// Last fire value for non-forgettable lists
3201		memory,
3202
3203		// Flag to know if list was already fired
3204		fired,
3205
3206		// Flag to prevent firing
3207		locked,
3208
3209		// Actual callback list
3210		list = [],
3211
3212		// Queue of execution data for repeatable lists
3213		queue = [],
3214
3215		// Index of currently firing callback (modified by add/remove as needed)
3216		firingIndex = -1,
3217
3218		// Fire callbacks
3219		fire = function() {
3220
3221			// Enforce single-firing
3222			locked = options.once;
3223
3224			// Execute callbacks for all pending executions,
3225			// respecting firingIndex overrides and runtime changes
3226			fired = firing = true;
3227			for ( ; queue.length; firingIndex = -1 ) {
3228				memory = queue.shift();
3229				while ( ++firingIndex < list.length ) {
3230
3231					// Run callback and check for early termination
3232					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
3233						options.stopOnFalse ) {
3234
3235						// Jump to end and forget the data so .add doesn't re-fire
3236						firingIndex = list.length;
3237						memory = false;
3238					}
3239				}
3240			}
3241
3242			// Forget the data if we're done with it
3243			if ( !options.memory ) {
3244				memory = false;
3245			}
3246
3247			firing = false;
3248
3249			// Clean up if we're done firing for good
3250			if ( locked ) {
3251
3252				// Keep an empty list if we have data for future add calls
3253				if ( memory ) {
3254					list = [];
3255
3256				// Otherwise, this object is spent
3257				} else {
3258					list = "";
3259				}
3260			}
3261		},
3262
3263		// Actual Callbacks object
3264		self = {
3265
3266			// Add a callback or a collection of callbacks to the list
3267			add: function() {
3268				if ( list ) {
3269
3270					// If we have memory from a past run, we should fire after adding
3271					if ( memory && !firing ) {
3272						firingIndex = list.length - 1;
3273						queue.push( memory );
3274					}
3275
3276					( function add( args ) {
3277						jQuery.each( args, function( _, arg ) {
3278							if ( jQuery.isFunction( arg ) ) {
3279								if ( !options.unique || !self.has( arg ) ) {
3280									list.push( arg );
3281								}
3282							} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
3283
3284								// Inspect recursively
3285								add( arg );
3286							}
3287						} );
3288					} )( arguments );
3289
3290					if ( memory && !firing ) {
3291						fire();
3292					}
3293				}
3294				return this;
3295			},
3296
3297			// Remove a callback from the list
3298			remove: function() {
3299				jQuery.each( arguments, function( _, arg ) {
3300					var index;
3301					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
3302						list.splice( index, 1 );
3303
3304						// Handle firing indexes
3305						if ( index <= firingIndex ) {
3306							firingIndex--;
3307						}
3308					}
3309				} );
3310				return this;
3311			},
3312
3313			// Check if a given callback is in the list.
3314			// If no argument is given, return whether or not list has callbacks attached.
3315			has: function( fn ) {
3316				return fn ?
3317					jQuery.inArray( fn, list ) > -1 :
3318					list.length > 0;
3319			},
3320
3321			// Remove all callbacks from the list
3322			empty: function() {
3323				if ( list ) {
3324					list = [];
3325				}
3326				return this;
3327			},
3328
3329			// Disable .fire and .add
3330			// Abort any current/pending executions
3331			// Clear all callbacks and values
3332			disable: function() {
3333				locked = queue = [];
3334				list = memory = "";
3335				return this;
3336			},
3337			disabled: function() {
3338				return !list;
3339			},
3340
3341			// Disable .fire
3342			// Also disable .add unless we have memory (since it would have no effect)
3343			// Abort any pending executions
3344			lock: function() {
3345				locked = true;
3346				if ( !memory ) {
3347					self.disable();
3348				}
3349				return this;
3350			},
3351			locked: function() {
3352				return !!locked;
3353			},
3354
3355			// Call all callbacks with the given context and arguments
3356			fireWith: function( context, args ) {
3357				if ( !locked ) {
3358					args = args || [];
3359					args = [ context, args.slice ? args.slice() : args ];
3360					queue.push( args );
3361					if ( !firing ) {
3362						fire();
3363					}
3364				}
3365				return this;
3366			},
3367
3368			// Call all the callbacks with the given arguments
3369			fire: function() {
3370				self.fireWith( this, arguments );
3371				return this;
3372			},
3373
3374			// To know if the callbacks have already been called at least once
3375			fired: function() {
3376				return !!fired;
3377			}
3378		};
3379
3380	return self;
3381};
3382
3383
3384jQuery.extend( {
3385
3386	Deferred: function( func ) {
3387		var tuples = [
3388
3389				// action, add listener, listener list, final state
3390				[ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
3391				[ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
3392				[ "notify", "progress", jQuery.Callbacks( "memory" ) ]
3393			],
3394			state = "pending",
3395			promise = {
3396				state: function() {
3397					return state;
3398				},
3399				always: function() {
3400					deferred.done( arguments ).fail( arguments );
3401					return this;
3402				},
3403				then: function( /* fnDone, fnFail, fnProgress */ ) {
3404					var fns = arguments;
3405					return jQuery.Deferred( function( newDefer ) {
3406						jQuery.each( tuples, function( i, tuple ) {
3407							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
3408
3409							// deferred[ done | fail | progress ] for forwarding actions to newDefer
3410							deferred[ tuple[ 1 ] ]( function() {
3411								var returned = fn && fn.apply( this, arguments );
3412								if ( returned && jQuery.isFunction( returned.promise ) ) {
3413									returned.promise()
3414										.progress( newDefer.notify )
3415										.done( newDefer.resolve )
3416										.fail( newDefer.reject );
3417								} else {
3418									newDefer[ tuple[ 0 ] + "With" ](
3419										this === promise ? newDefer.promise() : this,
3420										fn ? [ returned ] : arguments
3421									);
3422								}
3423							} );
3424						} );
3425						fns = null;
3426					} ).promise();
3427				},
3428
3429				// Get a promise for this deferred
3430				// If obj is provided, the promise aspect is added to the object
3431				promise: function( obj ) {
3432					return obj != null ? jQuery.extend( obj, promise ) : promise;
3433				}
3434			},
3435			deferred = {};
3436
3437		// Keep pipe for back-compat
3438		promise.pipe = promise.then;
3439
3440		// Add list-specific methods
3441		jQuery.each( tuples, function( i, tuple ) {
3442			var list = tuple[ 2 ],
3443				stateString = tuple[ 3 ];
3444
3445			// promise[ done | fail | progress ] = list.add
3446			promise[ tuple[ 1 ] ] = list.add;
3447
3448			// Handle state
3449			if ( stateString ) {
3450				list.add( function() {
3451
3452					// state = [ resolved | rejected ]
3453					state = stateString;
3454
3455				// [ reject_list | resolve_list ].disable; progress_list.lock
3456				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
3457			}
3458
3459			// deferred[ resolve | reject | notify ]
3460			deferred[ tuple[ 0 ] ] = function() {
3461				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );
3462				return this;
3463			};
3464			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
3465		} );
3466
3467		// Make the deferred a promise
3468		promise.promise( deferred );
3469
3470		// Call given func if any
3471		if ( func ) {
3472			func.call( deferred, deferred );
3473		}
3474
3475		// All done!
3476		return deferred;
3477	},
3478
3479	// Deferred helper
3480	when: function( subordinate /* , ..., subordinateN */ ) {
3481		var i = 0,
3482			resolveValues = slice.call( arguments ),
3483			length = resolveValues.length,
3484
3485			// the count of uncompleted subordinates
3486			remaining = length !== 1 ||
3487				( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
3488
3489			// the master Deferred.
3490			// If resolveValues consist of only a single Deferred, just use that.
3491			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
3492
3493			// Update function for both resolve and progress values
3494			updateFunc = function( i, contexts, values ) {
3495				return function( value ) {
3496					contexts[ i ] = this;
3497					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
3498					if ( values === progressValues ) {
3499						deferred.notifyWith( contexts, values );
3500
3501					} else if ( !( --remaining ) ) {
3502						deferred.resolveWith( contexts, values );
3503					}
3504				};
3505			},
3506
3507			progressValues, progressContexts, resolveContexts;
3508
3509		// add listeners to Deferred subordinates; treat others as resolved
3510		if ( length > 1 ) {
3511			progressValues = new Array( length );
3512			progressContexts = new Array( length );
3513			resolveContexts = new Array( length );
3514			for ( ; i < length; i++ ) {
3515				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
3516					resolveValues[ i ].promise()
3517						.progress( updateFunc( i, progressContexts, progressValues ) )
3518						.done( updateFunc( i, resolveContexts, resolveValues ) )
3519						.fail( deferred.reject );
3520				} else {
3521					--remaining;
3522				}
3523			}
3524		}
3525
3526		// if we're not waiting on anything, resolve the master
3527		if ( !remaining ) {
vendor: 2,149 bytes, lines 3528-3627
3528			deferred.resolveWith( resolveContexts, resolveValues );
3529		}
3530
3531		return deferred.promise();
3532	}
3533} );
3534
3535
3536// The deferred used on DOM ready
3537var readyList;
3538
3539jQuery.fn.ready = function( fn ) {
3540
3541	// Add the callback
3542	jQuery.ready.promise().done( fn );
3543
3544	return this;
3545};
3546
3547jQuery.extend( {
3548
3549	// Is the DOM ready to be used? Set to true once it occurs.
3550	isReady: false,
3551
3552	// A counter to track how many items to wait for before
3553	// the ready event fires. See #6781
3554	readyWait: 1,
3555
3556	// Hold (or release) the ready event
3557	holdReady: function( hold ) {
3558		if ( hold ) {
3559			jQuery.readyWait++;
3560		} else {
3561			jQuery.ready( true );
3562		}
3563	},
3564
3565	// Handle when the DOM is ready
3566	ready: function( wait ) {
3567
3568		// Abort if there are pending holds or we're already ready
3569		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
3570			return;
3571		}
3572
3573		// Remember that the DOM is ready
3574		jQuery.isReady = true;
3575
3576		// If a normal DOM Ready event fired, decrement, and wait if need be
3577		if ( wait !== true && --jQuery.readyWait > 0 ) {
3578			return;
3579		}
3580
3581		// If there are functions bound, to execute
3582		readyList.resolveWith( document, [ jQuery ] );
3583
3584		// Trigger any bound ready events
3585		if ( jQuery.fn.triggerHandler ) {
3586			jQuery( document ).triggerHandler( "ready" );
3587			jQuery( document ).off( "ready" );
3588		}
3589	}
3590} );
3591
3592/**
3593 * Clean-up method for dom ready events
3594 */
3595function detach() {
3596	if ( document.addEventListener ) {
3597		document.removeEventListener( "DOMContentLoaded", completed );
3598		window.removeEventListener( "load", completed );
3599
3600	} else {
3601		document.detachEvent( "onreadystatechange", completed );
3602		window.detachEvent( "onload", completed );
3603	}
3604}
3605
3606/**
3607 * The ready event handler and self cleanup method
3608 */
3609function completed() {
3610
3611	// readyState === "complete" is good enough for us to call the dom ready in oldIE
3612	if ( document.addEventListener ||
3613		window.event.type === "load" ||
3614		document.readyState === "complete" ) {
3615
3616		detach();
3617		jQuery.ready();
3618	}
3619}
3620
3621jQuery.ready.promise = function( obj ) {
3622	if ( !readyList ) {
3623
3624		readyList = jQuery.Deferred();
3625
3626		// Catch cases where $(document).ready() is called
3627		// after the browser event has already occurred.
3628		// Support: IE6-10
3629		// Older IE sometimes signals "interactive" too soon
3630		if ( document.readyState === "complete" ||
3631			( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
3632
3633			// Handle it asynchronously to allow scripts the opportunity to delay ready
3634			window.setTimeout( jQuery.ready );
3635
3636		// Standards-based browsers support DOMContentLoaded
3637		} else if ( document.addEventListener ) {
3638
3639			// Use the handy event callback
3640			document.addEventListener( "DOMContentLoaded", completed );
3641
3642			// A fallback to window.onload, that will always work
3643			window.addEventListener( "load", completed );
3644
3645		// If IE event model is used
3646		} else {
3647
3648			// Ensure firing before onload, maybe late but safe also for iframes
3649			document.attachEvent( "onreadystatechange", completed );
3650
3651			// A fallback to window.onload, that will always work
3652			window.attachEvent( "onload", completed );
3653
3654			// If IE and not a frame
3655			// continually check to see if the document is ready
3656			var top = false;
3657
3658			try {
3659				top = window.frameElement == null && document.documentElement;
3660			} catch ( e ) {}
3661
3662			if ( top && top.doScroll ) {
3663				( function doScrollCheck() {
3664					if ( !jQuery.isReady ) {
3665
3666						try {
3667
3668							// Use the trick by Diego Perini
3669							// http://javascript.nwbox.com/IEContentLoaded/
3670							top.doScroll( "left" );
3671						} catch ( e ) {
3672							return window.setTimeout( doScrollCheck, 50 );
3673						}
3674
3675						// detach all dom ready events
3676						detach();
3677
3678						// and execute any waiting functions
3679						jQuery.ready();
3680					}
3681				} )();
3682			}
3683		}
3684	}
3685	return readyList.promise( obj );
3686};
3687
3688// Kick off the DOM ready check even if the user does not
3689jQuery.ready.promise();
3690
3691
3692
3693
3694// Support: IE<9
3695// Iteration over object's inherited properties before its own
3696var i;
3697for ( i in jQuery( support ) ) {
3698	break;
3699}
3700support.ownFirst = i === "0";
3701
3702// Note: most support tests are defined in their respective modules.
3703// false until the test is run
3704support.inlineBlockNeedsLayout = false;
3705
3706// Execute ASAP in case we need to set body.style.zoom
3707jQuery( function() {
3708
3709	// Minified: var a,b,c,d
3710	var val, div, body, container;
3711
3712	body = document.getElementsByTagName( "body" )[ 0 ];
3713	if ( !body || !body.style ) {
3714
3715		// Return for frameset docs that don't have a body
3716		return;
3717	}
3718
3719	// Setup
3720	div = document.createElement( "div" );
3721	container = document.createElement( "div" );
3722	container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
3723	body.appendChild( container ).appendChild( div );
3724
3725	if ( typeof div.style.zoom !== "undefined" ) {
3726
3727		// Support: IE<8
3728		// Check if natively block-level elements act like inline-block
3729		// elements when setting their display to 'inline' and giving
3730		// them layout
3731		div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1";
3732
3733		support.inlineBlockNeedsLayout = val = div.offsetWidth === 3;
3734		if ( val ) {
3735
3736			// Prevent IE 6 from affecting layout for positioned elements #11048
3737			// Prevent IE from shrinking the body in IE 7 mode #12869
3738			// Support: IE<8
3739			body.style.zoom = 1;
3740		}
3741	}
3742
3743	body.removeChild( container );
3744} );
3745
3746
3747( function() {
3748	var div = document.createElement( "div" );
3749
3750	// Support: IE<9
3751	support.deleteExpando = true;
3752	try {
3753		delete div.test;
3754	} catch ( e ) {
3755		support.deleteExpando = false;
3756	}
3757
3758	// Null elements to avoid leaks in IE.
3759	div = null;
3760} )();
3761var acceptData = function( elem ) {
3762	var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ],
3763		nodeType = +elem.nodeType || 1;
3764
3765	// Do not set data on non-element DOM nodes because it will not be cleared (#8335).
3766	return nodeType !== 1 && nodeType !== 9 ?
3767		false :
3768
3769		// Nodes accept data unless otherwise specified; rejection can be conditional
3770		!noData || noData !== true && elem.getAttribute( "classid" ) === noData;
3771};
3772
3773
3774
3775
3776var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
3777	rmultiDash = /([A-Z])/g;
3778
3779function dataAttr( elem, key, data ) {
3780
3781	// If nothing was found internally, try to fetch any
3782	// data from the HTML5 data-* attribute
3783	if ( data === undefined && elem.nodeType === 1 ) {
3784
3785		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
3786
3787		data = elem.getAttribute( name );
3788
3789		if ( typeof data === "string" ) {
3790			try {
3791				data = data === "true" ? true :
vendor: 11,186 bytes, lines 3792-4256
3792					data === "false" ? false :
3793					data === "null" ? null :
3794
3795					// Only convert to a number if it doesn't change the string
3796					+data + "" === data ? +data :
3797					rbrace.test( data ) ? jQuery.parseJSON( data ) :
3798					data;
3799			} catch ( e ) {}
3800
3801			// Make sure we set the data so it isn't changed later
3802			jQuery.data( elem, key, data );
3803
3804		} else {
3805			data = undefined;
3806		}
3807	}
3808
3809	return data;
3810}
3811
3812// checks a cache object for emptiness
3813function isEmptyDataObject( obj ) {
3814	var name;
3815	for ( name in obj ) {
3816
3817		// if the public data object is empty, the private is still empty
3818		if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) {
3819			continue;
3820		}
3821		if ( name !== "toJSON" ) {
3822			return false;
3823		}
3824	}
3825
3826	return true;
3827}
3828
3829function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
3830	if ( !acceptData( elem ) ) {
3831		return;
3832	}
3833
3834	var ret, thisCache,
3835		internalKey = jQuery.expando,
3836
3837		// We have to handle DOM nodes and JS objects differently because IE6-7
3838		// can't GC object references properly across the DOM-JS boundary
3839		isNode = elem.nodeType,
3840
3841		// Only DOM nodes need the global jQuery cache; JS object data is
3842		// attached directly to the object so GC can occur automatically
3843		cache = isNode ? jQuery.cache : elem,
3844
3845		// Only defining an ID for JS objects if its cache already exists allows
3846		// the code to shortcut on the same path as a DOM node with no cache
3847		id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
3848
3849	// Avoid doing any more work than we need to when trying to get data on an
3850	// object that has no data at all
3851	if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) &&
3852		data === undefined && typeof name === "string" ) {
3853		return;
3854	}
3855
3856	if ( !id ) {
3857
3858		// Only DOM nodes need a new unique ID for each element since their data
3859		// ends up in the global cache
3860		if ( isNode ) {
3861			id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
3862		} else {
3863			id = internalKey;
3864		}
3865	}
3866
3867	if ( !cache[ id ] ) {
3868
3869		// Avoid exposing jQuery metadata on plain JS objects when the object
3870		// is serialized using JSON.stringify
3871		cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
3872	}
3873
3874	// An object can be passed to jQuery.data instead of a key/value pair; this gets
3875	// shallow copied over onto the existing cache
3876	if ( typeof name === "object" || typeof name === "function" ) {
3877		if ( pvt ) {
3878			cache[ id ] = jQuery.extend( cache[ id ], name );
3879		} else {
3880			cache[ id ].data = jQuery.extend( cache[ id ].data, name );
3881		}
3882	}
3883
3884	thisCache = cache[ id ];
3885
3886	// jQuery data() is stored in a separate object inside the object's internal data
3887	// cache in order to avoid key collisions between internal data and user-defined
3888	// data.
3889	if ( !pvt ) {
3890		if ( !thisCache.data ) {
3891			thisCache.data = {};
3892		}
3893
3894		thisCache = thisCache.data;
3895	}
3896
3897	if ( data !== undefined ) {
3898		thisCache[ jQuery.camelCase( name ) ] = data;
3899	}
3900
3901	// Check for both converted-to-camel and non-converted data property names
3902	// If a data property was specified
3903	if ( typeof name === "string" ) {
3904
3905		// First Try to find as-is property data
3906		ret = thisCache[ name ];
3907
3908		// Test for null|undefined property data
3909		if ( ret == null ) {
3910
3911			// Try to find the camelCased property
3912			ret = thisCache[ jQuery.camelCase( name ) ];
3913		}
3914	} else {
3915		ret = thisCache;
3916	}
3917
3918	return ret;
3919}
3920
3921function internalRemoveData( elem, name, pvt ) {
3922	if ( !acceptData( elem ) ) {
3923		return;
3924	}
3925
3926	var thisCache, i,
3927		isNode = elem.nodeType,
3928
3929		// See jQuery.data for more information
3930		cache = isNode ? jQuery.cache : elem,
3931		id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
3932
3933	// If there is already no cache entry for this object, there is no
3934	// purpose in continuing
3935	if ( !cache[ id ] ) {
3936		return;
3937	}
3938
3939	if ( name ) {
3940
3941		thisCache = pvt ? cache[ id ] : cache[ id ].data;
3942
3943		if ( thisCache ) {
3944
3945			// Support array or space separated string names for data keys
3946			if ( !jQuery.isArray( name ) ) {
3947
3948				// try the string as a key before any manipulation
3949				if ( name in thisCache ) {
3950					name = [ name ];
3951				} else {
3952
3953					// split the camel cased version by spaces unless a key with the spaces exists
3954					name = jQuery.camelCase( name );
3955					if ( name in thisCache ) {
3956						name = [ name ];
3957					} else {
3958						name = name.split( " " );
3959					}
3960				}
3961			} else {
3962
3963				// If "name" is an array of keys...
3964				// When data is initially created, via ("key", "val") signature,
3965				// keys will be converted to camelCase.
3966				// Since there is no way to tell _how_ a key was added, remove
3967				// both plain key and camelCase key. #12786
3968				// This will only penalize the array argument path.
3969				name = name.concat( jQuery.map( name, jQuery.camelCase ) );
3970			}
3971
3972			i = name.length;
3973			while ( i-- ) {
3974				delete thisCache[ name[ i ] ];
3975			}
3976
3977			// If there is no data left in the cache, we want to continue
3978			// and let the cache object itself get destroyed
3979			if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) {
3980				return;
3981			}
3982		}
3983	}
3984
3985	// See jQuery.data for more information
3986	if ( !pvt ) {
3987		delete cache[ id ].data;
3988
3989		// Don't destroy the parent cache unless the internal data object
3990		// had been the only thing left in it
3991		if ( !isEmptyDataObject( cache[ id ] ) ) {
3992			return;
3993		}
3994	}
3995
3996	// Destroy the cache
3997	if ( isNode ) {
3998		jQuery.cleanData( [ elem ], true );
3999
4000	// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
4001	/* jshint eqeqeq: false */
4002	} else if ( support.deleteExpando || cache != cache.window ) {
4003		/* jshint eqeqeq: true */
4004		delete cache[ id ];
4005
4006	// When all else fails, undefined
4007	} else {
4008		cache[ id ] = undefined;
4009	}
4010}
4011
4012jQuery.extend( {
4013	cache: {},
4014
4015	// The following elements (space-suffixed to avoid Object.prototype collisions)
4016	// throw uncatchable exceptions if you attempt to set expando properties
4017	noData: {
4018		"applet ": true,
4019		"embed ": true,
4020
4021		// ...but Flash objects (which have this classid) *can* handle expandos
4022		"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
4023	},
4024
4025	hasData: function( elem ) {
4026		elem = elem.nodeType ? jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ];
4027		return !!elem && !isEmptyDataObject( elem );
4028	},
4029
4030	data: function( elem, name, data ) {
4031		return internalData( elem, name, data );
4032	},
4033
4034	removeData: function( elem, name ) {
4035		return internalRemoveData( elem, name );
4036	},
4037
4038	// For internal use only.
4039	_data: function( elem, name, data ) {
4040		return internalData( elem, name, data, true );
4041	},
4042
4043	_removeData: function( elem, name ) {
4044		return internalRemoveData( elem, name, true );
4045	}
4046} );
4047
4048jQuery.fn.extend( {
4049	data: function( key, value ) {
4050		var i, name, data,
4051			elem = this[ 0 ],
4052			attrs = elem && elem.attributes;
4053
4054		// Special expections of .data basically thwart jQuery.access,
4055		// so implement the relevant behavior ourselves
4056
4057		// Gets all values
4058		if ( key === undefined ) {
4059			if ( this.length ) {
4060				data = jQuery.data( elem );
4061
4062				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
4063					i = attrs.length;
4064					while ( i-- ) {
4065
4066						// Support: IE11+
4067						// The attrs elements can be null (#14894)
4068						if ( attrs[ i ] ) {
4069							name = attrs[ i ].name;
4070							if ( name.indexOf( "data-" ) === 0 ) {
4071								name = jQuery.camelCase( name.slice( 5 ) );
4072								dataAttr( elem, name, data[ name ] );
4073							}
4074						}
4075					}
4076					jQuery._data( elem, "parsedAttrs", true );
4077				}
4078			}
4079
4080			return data;
4081		}
4082
4083		// Sets multiple values
4084		if ( typeof key === "object" ) {
4085			return this.each( function() {
4086				jQuery.data( this, key );
4087			} );
4088		}
4089
4090		return arguments.length > 1 ?
4091
4092			// Sets one value
4093			this.each( function() {
4094				jQuery.data( this, key, value );
4095			} ) :
4096
4097			// Gets one value
4098			// Try to fetch any internally stored data first
4099			elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
4100	},
4101
4102	removeData: function( key ) {
4103		return this.each( function() {
4104			jQuery.removeData( this, key );
4105		} );
4106	}
4107} );
4108
4109
4110jQuery.extend( {
4111	queue: function( elem, type, data ) {
4112		var queue;
4113
4114		if ( elem ) {
4115			type = ( type || "fx" ) + "queue";
4116			queue = jQuery._data( elem, type );
4117
4118			// Speed up dequeue by getting out quickly if this is just a lookup
4119			if ( data ) {
4120				if ( !queue || jQuery.isArray( data ) ) {
4121					queue = jQuery._data( elem, type, jQuery.makeArray( data ) );
4122				} else {
4123					queue.push( data );
4124				}
4125			}
4126			return queue || [];
4127		}
4128	},
4129
4130	dequeue: function( elem, type ) {
4131		type = type || "fx";
4132
4133		var queue = jQuery.queue( elem, type ),
4134			startLength = queue.length,
4135			fn = queue.shift(),
4136			hooks = jQuery._queueHooks( elem, type ),
4137			next = function() {
4138				jQuery.dequeue( elem, type );
4139			};
4140
4141		// If the fx queue is dequeued, always remove the progress sentinel
4142		if ( fn === "inprogress" ) {
4143			fn = queue.shift();
4144			startLength--;
4145		}
4146
4147		if ( fn ) {
4148
4149			// Add a progress sentinel to prevent the fx queue from being
4150			// automatically dequeued
4151			if ( type === "fx" ) {
4152				queue.unshift( "inprogress" );
4153			}
4154
4155			// clear up the last queue stop function
4156			delete hooks.stop;
4157			fn.call( elem, next, hooks );
4158		}
4159
4160		if ( !startLength && hooks ) {
4161			hooks.empty.fire();
4162		}
4163	},
4164
4165	// not intended for public consumption - generates a queueHooks object,
4166	// or returns the current one
4167	_queueHooks: function( elem, type ) {
4168		var key = type + "queueHooks";
4169		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
4170			empty: jQuery.Callbacks( "once memory" ).add( function() {
4171				jQuery._removeData( elem, type + "queue" );
4172				jQuery._removeData( elem, key );
4173			} )
4174		} );
4175	}
4176} );
4177
4178jQuery.fn.extend( {
4179	queue: function( type, data ) {
4180		var setter = 2;
4181
4182		if ( typeof type !== "string" ) {
4183			data = type;
4184			type = "fx";
4185			setter--;
4186		}
4187
4188		if ( arguments.length < setter ) {
4189			return jQuery.queue( this[ 0 ], type );
4190		}
4191
4192		return data === undefined ?
4193			this :
4194			this.each( function() {
4195				var queue = jQuery.queue( this, type, data );
4196
4197				// ensure a hooks for this queue
4198				jQuery._queueHooks( this, type );
4199
4200				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
4201					jQuery.dequeue( this, type );
4202				}
4203			} );
4204	},
4205	dequeue: function( type ) {
4206		return this.each( function() {
4207			jQuery.dequeue( this, type );
4208		} );
4209	},
4210	clearQueue: function( type ) {
4211		return this.queue( type || "fx", [] );
4212	},
4213
4214	// Get a promise resolved when queues of a certain type
4215	// are emptied (fx is the type by default)
4216	promise: function( type, obj ) {
4217		var tmp,
4218			count = 1,
4219			defer = jQuery.Deferred(),
4220			elements = this,
4221			i = this.length,
4222			resolve = function() {
4223				if ( !( --count ) ) {
4224					defer.resolveWith( elements, [ elements ] );
4225				}
4226			};
4227
4228		if ( typeof type !== "string" ) {
4229			obj = type;
4230			type = undefined;
4231		}
4232		type = type || "fx";
4233
4234		while ( i-- ) {
4235			tmp = jQuery._data( elements[ i ], type + "queueHooks" );
4236			if ( tmp && tmp.empty ) {
4237				count++;
4238				tmp.empty.add( resolve );
4239			}
4240		}
4241		resolve();
4242		return defer.promise( obj );
4243	}
4244} );
4245
4246
4247( function() {
4248	var shrinkWrapBlocksVal;
4249
4250	support.shrinkWrapBlocks = function() {
4251		if ( shrinkWrapBlocksVal != null ) {
4252			return shrinkWrapBlocksVal;
4253		}
4254
4255		// Will be changed later if needed.
4256		shrinkWrapBlocksVal = false;
vendor: 4,171 bytes, lines 4257-4405
4257
4258		// Minified: var b,c,d
4259		var div, body, container;
4260
4261		body = document.getElementsByTagName( "body" )[ 0 ];
4262		if ( !body || !body.style ) {
4263
4264			// Test fired too early or in an unsupported environment, exit.
4265			return;
4266		}
4267
4268		// Setup
4269		div = document.createElement( "div" );
4270		container = document.createElement( "div" );
4271		container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
4272		body.appendChild( container ).appendChild( div );
4273
4274		// Support: IE6
4275		// Check if elements with layout shrink-wrap their children
4276		if ( typeof div.style.zoom !== "undefined" ) {
4277
4278			// Reset CSS: box-sizing; display; margin; border
4279			div.style.cssText =
4280
4281				// Support: Firefox<29, Android 2.3
4282				// Vendor-prefix box-sizing
4283				"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
4284				"box-sizing:content-box;display:block;margin:0;border:0;" +
4285				"padding:1px;width:1px;zoom:1";
4286			div.appendChild( document.createElement( "div" ) ).style.width = "5px";
4287			shrinkWrapBlocksVal = div.offsetWidth !== 3;
4288		}
4289
4290		body.removeChild( container );
4291
4292		return shrinkWrapBlocksVal;
4293	};
4294
4295} )();
4296var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
4297
4298var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
4299
4300
4301var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
4302
4303var isHidden = function( elem, el ) {
4304
4305		// isHidden might be called from jQuery#filter function;
4306		// in that case, element will be second argument
4307		elem = el || elem;
4308		return jQuery.css( elem, "display" ) === "none" ||
4309			!jQuery.contains( elem.ownerDocument, elem );
4310	};
4311
4312
4313
4314function adjustCSS( elem, prop, valueParts, tween ) {
4315	var adjusted,
4316		scale = 1,
4317		maxIterations = 20,
4318		currentValue = tween ?
4319			function() { return tween.cur(); } :
4320			function() { return jQuery.css( elem, prop, "" ); },
4321		initial = currentValue(),
4322		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
4323
4324		// Starting value computation is required for potential unit mismatches
4325		initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
4326			rcssNum.exec( jQuery.css( elem, prop ) );
4327
4328	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
4329
4330		// Trust units reported by jQuery.css
4331		unit = unit || initialInUnit[ 3 ];
4332
4333		// Make sure we update the tween properties later on
4334		valueParts = valueParts || [];
4335
4336		// Iteratively approximate from a nonzero starting point
4337		initialInUnit = +initial || 1;
4338
4339		do {
4340
4341			// If previous iteration zeroed out, double until we get *something*.
4342			// Use string for doubling so we don't accidentally see scale as unchanged below
4343			scale = scale || ".5";
4344
4345			// Adjust and apply
4346			initialInUnit = initialInUnit / scale;
4347			jQuery.style( elem, prop, initialInUnit + unit );
4348
4349		// Update scale, tolerating zero or NaN from tween.cur()
4350		// Break the loop if scale is unchanged or perfect, or if we've just had enough.
4351		} while (
4352			scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
4353		);
4354	}
4355
4356	if ( valueParts ) {
4357		initialInUnit = +initialInUnit || +initial || 0;
4358
4359		// Apply relative offset (+=/-=) if specified
4360		adjusted = valueParts[ 1 ] ?
4361			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
4362			+valueParts[ 2 ];
4363		if ( tween ) {
4364			tween.unit = unit;
4365			tween.start = initialInUnit;
4366			tween.end = adjusted;
4367		}
4368	}
4369	return adjusted;
4370}
4371
4372
4373// Multifunctional method to get and set values of a collection
4374// The value/s can optionally be executed if it's a function
4375var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
4376	var i = 0,
4377		length = elems.length,
4378		bulk = key == null;
4379
4380	// Sets many values
4381	if ( jQuery.type( key ) === "object" ) {
4382		chainable = true;
4383		for ( i in key ) {
4384			access( elems, fn, i, key[ i ], true, emptyGet, raw );
4385		}
4386
4387	// Sets one value
4388	} else if ( value !== undefined ) {
4389		chainable = true;
4390
4391		if ( !jQuery.isFunction( value ) ) {
4392			raw = true;
4393		}
4394
4395		if ( bulk ) {
4396
4397			// Bulk operations run against the entire set
4398			if ( raw ) {
4399				fn.call( elems, value );
4400				fn = null;
4401
4402			// ...except when executing function values
4403			} else {
4404				bulk = fn;
4405				fn = function( elem, key, value ) {
4406					return bulk.call( jQuery( elem ), value );
4407				};
4408			}
4409		}
4410
4411		if ( fn ) {
4412			for ( ; i < length; i++ ) {
4413				fn(
4414					elems[ i ],
4415					key,
4416					raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
4417				);
4418			}
4419		}
4420	}
4421
4422	return chainable ?
4423		elems :
4424
4425		// Gets
4426		bulk ?
4427			fn.call( elems ) :
4428			length ? fn( elems[ 0 ], key ) : emptyGet;
4429};
4430var rcheckableType = ( /^(?:checkbox|radio)$/i );
4431
4432var rtagName = ( /<([\w:-]+)/ );
4433
4434var rscriptType = ( /^$|\/(?:java|ecma)script/i );
4435
4436var rleadingWhitespace = ( /^\s+/ );
4437
4438var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|" +
4439		"details|dialog|figcaption|figure|footer|header|hgroup|main|" +
4440		"mark|meter|nav|output|picture|progress|section|summary|template|time|video";
4441
4442
4443
4444function createSafeFragment( document ) {
4445	var list = nodeNames.split( "|" ),
4446		safeFrag = document.createDocumentFragment();
4447
4448	if ( safeFrag.createElement ) {
4449		while ( list.length ) {
4450			safeFrag.createElement(
4451				list.pop()
4452			);
4453		}
4454	}
4455	return safeFrag;
4456}
4457
4458
4459( function() {
4460	var div = document.createElement( "div" ),
4461		fragment = document.createDocumentFragment(),
4462		input = document.createElement( "input" );
4463
4464	// Setup
4465	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
4466
4467	// IE strips leading whitespace when .innerHTML is used
4468	support.leadingWhitespace = div.firstChild.nodeType === 3;
4469
4470	// Make sure that tbody elements aren't automatically inserted
4471	// IE will insert them into empty tables
4472	support.tbody = !div.getElementsByTagName( "tbody" ).length;
4473
4474	// Make sure that link elements get serialized correctly by innerHTML
4475	// This requires a wrapper element in IE
4476	support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;
4477
4478	// Makes sure cloning an html5 element does not cause problems
4479	// Where outerHTML is undefined, this still works
4480	support.html5Clone =
4481		document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav></:nav>";
4482
4483	// Check if a disconnected checkbox will retain its checked
4484	// value of true after appended to the DOM (IE6/7)
4485	input.type = "checkbox";
4486	input.checked = true;
4487	fragment.appendChild( input );
4488	support.appendChecked = input.checked;
4489
4490	// Make sure textarea (and checkbox) defaultValue is properly cloned
4491	// Support: IE6-IE11+
4492	div.innerHTML = "<textarea>x</textarea>";
4493	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
4494
4495	// #11217 - WebKit loses check when the name is after the checked attribute
4496	fragment.appendChild( div );
4497
4498	// Support: Windows Web Apps (WWA)
4499	// `name` and `type` must use .setAttribute for WWA (#14901)
4500	input = document.createElement( "input" );
4501	input.setAttribute( "type", "radio" );
4502	input.setAttribute( "checked", "checked" );
4503	input.setAttribute( "name", "t" );
4504
4505	div.appendChild( input );
4506
4507	// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
4508	// old WebKit doesn't clone checked state correctly in fragments
4509	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
4510
4511	// Support: IE<9
4512	// Cloned elements keep attachEvent handlers, we use addEventListener on IE9+
4513	support.noCloneEvent = !!div.addEventListener;
4514
4515	// Support: IE<9
4516	// Since attributes and properties are the same in IE,
4517	// cleanData must set properties to undefined rather than use removeAttribute
4518	div[ jQuery.expando ] = 1;
4519	support.attributes = !div.getAttribute( jQuery.expando );
4520} )();
4521
4522
4523// We have to close these tags to support XHTML (#13200)
4524var wrapMap = {
4525	option: [ 1, "<select multiple='multiple'>", "</select>" ],
4526	legend: [ 1, "<fieldset>", "</fieldset>" ],
4527	area: [ 1, "<map>", "</map>" ],
4528
4529	// Support: IE8
4530	param: [ 1, "<object>", "</object>" ],
4531	thead: [ 1, "<table>", "</table>" ],
4532	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
4533	col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
4534	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
4535
4536	// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
4537	// unless wrapped in a div with non-breaking characters in front of it.
4538	_default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>" ]
4539};
4540
4541// Support: IE8-IE9
4542wrapMap.optgroup = wrapMap.option;
4543
4544wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
4545wrapMap.th = wrapMap.td;
4546
4547
4548function getAll( context, tag ) {
4549	var elems, elem,
4550		i = 0,
4551		found = typeof context.getElementsByTagName !== "undefined" ?
4552			context.getElementsByTagName( tag || "*" ) :
4553			typeof context.querySelectorAll !== "undefined" ?
4554				context.querySelectorAll( tag || "*" ) :
4555				undefined;
4556
4557	if ( !found ) {
4558		for ( found = [], elems = context.childNodes || context;
4559			( elem = elems[ i ] ) != null;
4560			i++
4561		) {
4562			if ( !tag || jQuery.nodeName( elem, tag ) ) {
4563				found.push( elem );
4564			} else {
4565				jQuery.merge( found, getAll( elem, tag ) );
4566			}
4567		}
4568	}
4569
4570	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
4571		jQuery.merge( [ context ], found ) :
4572		found;
4573}
4574
4575
4576// Mark scripts as having already been evaluated
4577function setGlobalEval( elems, refElements ) {
4578	var elem,
4579		i = 0;
4580	for ( ; ( elem = elems[ i ] ) != null; i++ ) {
4581		jQuery._data(
4582			elem,
4583			"globalEval",
4584			!refElements || jQuery._data( refElements[ i ], "globalEval" )
4585		);
4586	}
4587}
4588
4589
vendor: 45,546 bytes, lines 4590-6222
4590var rhtml = /<|&#?\w+;/,
4591	rtbody = /<tbody/i;
4592
4593function fixDefaultChecked( elem ) {
4594	if ( rcheckableType.test( elem.type ) ) {
4595		elem.defaultChecked = elem.checked;
4596	}
4597}
4598
4599function buildFragment( elems, context, scripts, selection, ignored ) {
4600	var j, elem, contains,
4601		tmp, tag, tbody, wrap,
4602		l = elems.length,
4603
4604		// Ensure a safe fragment
4605		safe = createSafeFragment( context ),
4606
4607		nodes = [],
4608		i = 0;
4609
4610	for ( ; i < l; i++ ) {
4611		elem = elems[ i ];
4612
4613		if ( elem || elem === 0 ) {
4614
4615			// Add nodes directly
4616			if ( jQuery.type( elem ) === "object" ) {
4617				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
4618
4619			// Convert non-html into a text node
4620			} else if ( !rhtml.test( elem ) ) {
4621				nodes.push( context.createTextNode( elem ) );
4622
4623			// Convert html into DOM nodes
4624			} else {
4625				tmp = tmp || safe.appendChild( context.createElement( "div" ) );
4626
4627				// Deserialize a standard representation
4628				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
4629				wrap = wrapMap[ tag ] || wrapMap._default;
4630
4631				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
4632
4633				// Descend through wrappers to the right content
4634				j = wrap[ 0 ];
4635				while ( j-- ) {
4636					tmp = tmp.lastChild;
4637				}
4638
4639				// Manually add leading whitespace removed by IE
4640				if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
4641					nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[ 0 ] ) );
4642				}
4643
4644				// Remove IE's autoinserted <tbody> from table fragments
4645				if ( !support.tbody ) {
4646
4647					// String was a <table>, *may* have spurious <tbody>
4648					elem = tag === "table" && !rtbody.test( elem ) ?
4649						tmp.firstChild :
4650
4651						// String was a bare <thead> or <tfoot>
4652						wrap[ 1 ] === "<table>" && !rtbody.test( elem ) ?
4653							tmp :
4654							0;
4655
4656					j = elem && elem.childNodes.length;
4657					while ( j-- ) {
4658						if ( jQuery.nodeName( ( tbody = elem.childNodes[ j ] ), "tbody" ) &&
4659							!tbody.childNodes.length ) {
4660
4661							elem.removeChild( tbody );
4662						}
4663					}
4664				}
4665
4666				jQuery.merge( nodes, tmp.childNodes );
4667
4668				// Fix #12392 for WebKit and IE > 9
4669				tmp.textContent = "";
4670
4671				// Fix #12392 for oldIE
4672				while ( tmp.firstChild ) {
4673					tmp.removeChild( tmp.firstChild );
4674				}
4675
4676				// Remember the top-level container for proper cleanup
4677				tmp = safe.lastChild;
4678			}
4679		}
4680	}
4681
4682	// Fix #11356: Clear elements from fragment
4683	if ( tmp ) {
4684		safe.removeChild( tmp );
4685	}
4686
4687	// Reset defaultChecked for any radios and checkboxes
4688	// about to be appended to the DOM in IE 6/7 (#8060)
4689	if ( !support.appendChecked ) {
4690		jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
4691	}
4692
4693	i = 0;
4694	while ( ( elem = nodes[ i++ ] ) ) {
4695
4696		// Skip elements already in the context collection (trac-4087)
4697		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
4698			if ( ignored ) {
4699				ignored.push( elem );
4700			}
4701
4702			continue;
4703		}
4704
4705		contains = jQuery.contains( elem.ownerDocument, elem );
4706
4707		// Append to fragment
4708		tmp = getAll( safe.appendChild( elem ), "script" );
4709
4710		// Preserve script evaluation history
4711		if ( contains ) {
4712			setGlobalEval( tmp );
4713		}
4714
4715		// Capture executables
4716		if ( scripts ) {
4717			j = 0;
4718			while ( ( elem = tmp[ j++ ] ) ) {
4719				if ( rscriptType.test( elem.type || "" ) ) {
4720					scripts.push( elem );
4721				}
4722			}
4723		}
4724	}
4725
4726	tmp = null;
4727
4728	return safe;
4729}
4730
4731
4732( function() {
4733	var i, eventName,
4734		div = document.createElement( "div" );
4735
4736	// Support: IE<9 (lack submit/change bubble), Firefox (lack focus(in | out) events)
4737	for ( i in { submit: true, change: true, focusin: true } ) {
4738		eventName = "on" + i;
4739
4740		if ( !( support[ i ] = eventName in window ) ) {
4741
4742			// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
4743			div.setAttribute( eventName, "t" );
4744			support[ i ] = div.attributes[ eventName ].expando === false;
4745		}
4746	}
4747
4748	// Null elements to avoid leaks in IE.
4749	div = null;
4750} )();
4751
4752
4753var rformElems = /^(?:input|select|textarea)$/i,
4754	rkeyEvent = /^key/,
4755	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
4756	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
4757	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
4758
4759function returnTrue() {
4760	return true;
4761}
4762
4763function returnFalse() {
4764	return false;
4765}
4766
4767// Support: IE9
4768// See #13393 for more info
4769function safeActiveElement() {
4770	try {
4771		return document.activeElement;
4772	} catch ( err ) { }
4773}
4774
4775function on( elem, types, selector, data, fn, one ) {
4776	var origFn, type;
4777
4778	// Types can be a map of types/handlers
4779	if ( typeof types === "object" ) {
4780
4781		// ( types-Object, selector, data )
4782		if ( typeof selector !== "string" ) {
4783
4784			// ( types-Object, data )
4785			data = data || selector;
4786			selector = undefined;
4787		}
4788		for ( type in types ) {
4789			on( elem, type, selector, data, types[ type ], one );
4790		}
4791		return elem;
4792	}
4793
4794	if ( data == null && fn == null ) {
4795
4796		// ( types, fn )
4797		fn = selector;
4798		data = selector = undefined;
4799	} else if ( fn == null ) {
4800		if ( typeof selector === "string" ) {
4801
4802			// ( types, selector, fn )
4803			fn = data;
4804			data = undefined;
4805		} else {
4806
4807			// ( types, data, fn )
4808			fn = data;
4809			data = selector;
4810			selector = undefined;
4811		}
4812	}
4813	if ( fn === false ) {
4814		fn = returnFalse;
4815	} else if ( !fn ) {
4816		return elem;
4817	}
4818
4819	if ( one === 1 ) {
4820		origFn = fn;
4821		fn = function( event ) {
4822
4823			// Can use an empty set, since event contains the info
4824			jQuery().off( event );
4825			return origFn.apply( this, arguments );
4826		};
4827
4828		// Use same guid so caller can remove using origFn
4829		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
4830	}
4831	return elem.each( function() {
4832		jQuery.event.add( this, types, fn, data, selector );
4833	} );
4834}
4835
4836/*
4837 * Helper functions for managing events -- not part of the public interface.
4838 * Props to Dean Edwards' addEvent library for many of the ideas.
4839 */
4840jQuery.event = {
4841
4842	global: {},
4843
4844	add: function( elem, types, handler, data, selector ) {
4845		var tmp, events, t, handleObjIn,
4846			special, eventHandle, handleObj,
4847			handlers, type, namespaces, origType,
4848			elemData = jQuery._data( elem );
4849
4850		// Don't attach events to noData or text/comment nodes (but allow plain objects)
4851		if ( !elemData ) {
4852			return;
4853		}
4854
4855		// Caller can pass in an object of custom data in lieu of the handler
4856		if ( handler.handler ) {
4857			handleObjIn = handler;
4858			handler = handleObjIn.handler;
4859			selector = handleObjIn.selector;
4860		}
4861
4862		// Make sure that the handler has a unique ID, used to find/remove it later
4863		if ( !handler.guid ) {
4864			handler.guid = jQuery.guid++;
4865		}
4866
4867		// Init the element's event structure and main handler, if this is the first
4868		if ( !( events = elemData.events ) ) {
4869			events = elemData.events = {};
4870		}
4871		if ( !( eventHandle = elemData.handle ) ) {
4872			eventHandle = elemData.handle = function( e ) {
4873
4874				// Discard the second event of a jQuery.event.trigger() and
4875				// when an event is called after a page has unloaded
4876				return typeof jQuery !== "undefined" &&
4877					( !e || jQuery.event.triggered !== e.type ) ?
4878					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
4879					undefined;
4880			};
4881
4882			// Add elem as a property of the handle fn to prevent a memory leak
4883			// with IE non-native events
4884			eventHandle.elem = elem;
4885		}
4886
4887		// Handle multiple events separated by a space
4888		types = ( types || "" ).match( rnotwhite ) || [ "" ];
4889		t = types.length;
4890		while ( t-- ) {
4891			tmp = rtypenamespace.exec( types[ t ] ) || [];
4892			type = origType = tmp[ 1 ];
4893			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
4894
4895			// There *must* be a type, no attaching namespace-only handlers
4896			if ( !type ) {
4897				continue;
4898			}
4899
4900			// If event changes its type, use the special event handlers for the changed type
4901			special = jQuery.event.special[ type ] || {};
4902
4903			// If selector defined, determine special event api type, otherwise given type
4904			type = ( selector ? special.delegateType : special.bindType ) || type;
4905
4906			// Update special based on newly reset type
4907			special = jQuery.event.special[ type ] || {};
4908
4909			// handleObj is passed to all event handlers
4910			handleObj = jQuery.extend( {
4911				type: type,
4912				origType: origType,
4913				data: data,
4914				handler: handler,
4915				guid: handler.guid,
4916				selector: selector,
4917				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
4918				namespace: namespaces.join( "." )
4919			}, handleObjIn );
4920
4921			// Init the event handler queue if we're the first
4922			if ( !( handlers = events[ type ] ) ) {
4923				handlers = events[ type ] = [];
4924				handlers.delegateCount = 0;
4925
4926				// Only use addEventListener/attachEvent if the special events handler returns false
4927				if ( !special.setup ||
4928					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
4929
4930					// Bind the global event handler to the element
4931					if ( elem.addEventListener ) {
4932						elem.addEventListener( type, eventHandle, false );
4933
4934					} else if ( elem.attachEvent ) {
4935						elem.attachEvent( "on" + type, eventHandle );
4936					}
4937				}
4938			}
4939
4940			if ( special.add ) {
4941				special.add.call( elem, handleObj );
4942
4943				if ( !handleObj.handler.guid ) {
4944					handleObj.handler.guid = handler.guid;
4945				}
4946			}
4947
4948			// Add to the element's handler list, delegates in front
4949			if ( selector ) {
4950				handlers.splice( handlers.delegateCount++, 0, handleObj );
4951			} else {
4952				handlers.push( handleObj );
4953			}
4954
4955			// Keep track of which events have ever been used, for event optimization
4956			jQuery.event.global[ type ] = true;
4957		}
4958
4959		// Nullify elem to prevent memory leaks in IE
4960		elem = null;
4961	},
4962
4963	// Detach an event or set of events from an element
4964	remove: function( elem, types, handler, selector, mappedTypes ) {
4965		var j, handleObj, tmp,
4966			origCount, t, events,
4967			special, handlers, type,
4968			namespaces, origType,
4969			elemData = jQuery.hasData( elem ) && jQuery._data( elem );
4970
4971		if ( !elemData || !( events = elemData.events ) ) {
4972			return;
4973		}
4974
4975		// Once for each type.namespace in types; type may be omitted
4976		types = ( types || "" ).match( rnotwhite ) || [ "" ];
4977		t = types.length;
4978		while ( t-- ) {
4979			tmp = rtypenamespace.exec( types[ t ] ) || [];
4980			type = origType = tmp[ 1 ];
4981			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
4982
4983			// Unbind all events (on this namespace, if provided) for the element
4984			if ( !type ) {
4985				for ( type in events ) {
4986					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
4987				}
4988				continue;
4989			}
4990
4991			special = jQuery.event.special[ type ] || {};
4992			type = ( selector ? special.delegateType : special.bindType ) || type;
4993			handlers = events[ type ] || [];
4994			tmp = tmp[ 2 ] &&
4995				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
4996
4997			// Remove matching events
4998			origCount = j = handlers.length;
4999			while ( j-- ) {
5000				handleObj = handlers[ j ];
5001
5002				if ( ( mappedTypes || origType === handleObj.origType ) &&
5003					( !handler || handler.guid === handleObj.guid ) &&
5004					( !tmp || tmp.test( handleObj.namespace ) ) &&
5005					( !selector || selector === handleObj.selector ||
5006						selector === "**" && handleObj.selector ) ) {
5007					handlers.splice( j, 1 );
5008
5009					if ( handleObj.selector ) {
5010						handlers.delegateCount--;
5011					}
5012					if ( special.remove ) {
5013						special.remove.call( elem, handleObj );
5014					}
5015				}
5016			}
5017
5018			// Remove generic event handler if we removed something and no more handlers exist
5019			// (avoids potential for endless recursion during removal of special event handlers)
5020			if ( origCount && !handlers.length ) {
5021				if ( !special.teardown ||
5022					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
5023
5024					jQuery.removeEvent( elem, type, elemData.handle );
5025				}
5026
5027				delete events[ type ];
5028			}
5029		}
5030
5031		// Remove the expando if it's no longer used
5032		if ( jQuery.isEmptyObject( events ) ) {
5033			delete elemData.handle;
5034
5035			// removeData also checks for emptiness and clears the expando if empty
5036			// so use it instead of delete
5037			jQuery._removeData( elem, "events" );
5038		}
5039	},
5040
5041	trigger: function( event, data, elem, onlyHandlers ) {
5042		var handle, ontype, cur,
5043			bubbleType, special, tmp, i,
5044			eventPath = [ elem || document ],
5045			type = hasOwn.call( event, "type" ) ? event.type : event,
5046			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
5047
5048		cur = tmp = elem = elem || document;
5049
5050		// Don't do events on text and comment nodes
5051		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
5052			return;
5053		}
5054
5055		// focus/blur morphs to focusin/out; ensure we're not firing them right now
5056		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
5057			return;
5058		}
5059
5060		if ( type.indexOf( "." ) > -1 ) {
5061
5062			// Namespaced trigger; create a regexp to match event type in handle()
5063			namespaces = type.split( "." );
5064			type = namespaces.shift();
5065			namespaces.sort();
5066		}
5067		ontype = type.indexOf( ":" ) < 0 && "on" + type;
5068
5069		// Caller can pass in a jQuery.Event object, Object, or just an event type string
5070		event = event[ jQuery.expando ] ?
5071			event :
5072			new jQuery.Event( type, typeof event === "object" && event );
5073
5074		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
5075		event.isTrigger = onlyHandlers ? 2 : 3;
5076		event.namespace = namespaces.join( "." );
5077		event.rnamespace = event.namespace ?
5078			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
5079			null;
5080
5081		// Clean up the event in case it is being reused
5082		event.result = undefined;
5083		if ( !event.target ) {
5084			event.target = elem;
5085		}
5086
5087		// Clone any incoming data and prepend the event, creating the handler arg list
5088		data = data == null ?
5089			[ event ] :
5090			jQuery.makeArray( data, [ event ] );
5091
5092		// Allow special events to draw outside the lines
5093		special = jQuery.event.special[ type ] || {};
5094		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
5095			return;
5096		}
5097
5098		// Determine event propagation path in advance, per W3C events spec (#9951)
5099		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
5100		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
5101
5102			bubbleType = special.delegateType || type;
5103			if ( !rfocusMorph.test( bubbleType + type ) ) {
5104				cur = cur.parentNode;
5105			}
5106			for ( ; cur; cur = cur.parentNode ) {
5107				eventPath.push( cur );
5108				tmp = cur;
5109			}
5110
5111			// Only add window if we got to document (e.g., not plain obj or detached DOM)
5112			if ( tmp === ( elem.ownerDocument || document ) ) {
5113				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
5114			}
5115		}
5116
5117		// Fire handlers on the event path
5118		i = 0;
5119		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
5120
5121			event.type = i > 1 ?
5122				bubbleType :
5123				special.bindType || type;
5124
5125			// jQuery handler
5126			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] &&
5127				jQuery._data( cur, "handle" );
5128
5129			if ( handle ) {
5130				handle.apply( cur, data );
5131			}
5132
5133			// Native handler
5134			handle = ontype && cur[ ontype ];
5135			if ( handle && handle.apply && acceptData( cur ) ) {
5136				event.result = handle.apply( cur, data );
5137				if ( event.result === false ) {
5138					event.preventDefault();
5139				}
5140			}
5141		}
5142		event.type = type;
5143
5144		// If nobody prevented the default action, do it now
5145		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
5146
5147			if (
5148				( !special._default ||
5149				 special._default.apply( eventPath.pop(), data ) === false
5150				) && acceptData( elem )
5151			) {
5152
5153				// Call a native DOM method on the target with the same name name as the event.
5154				// Can't use an .isFunction() check here because IE6/7 fails that test.
5155				// Don't do default actions on window, that's where global variables be (#6170)
5156				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
5157
5158					// Don't re-trigger an onFOO event when we call its FOO() method
5159					tmp = elem[ ontype ];
5160
5161					if ( tmp ) {
5162						elem[ ontype ] = null;
5163					}
5164
5165					// Prevent re-triggering of the same event, since we already bubbled it above
5166					jQuery.event.triggered = type;
5167					try {
5168						elem[ type ]();
5169					} catch ( e ) {
5170
5171						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
5172						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
5173					}
5174					jQuery.event.triggered = undefined;
5175
5176					if ( tmp ) {
5177						elem[ ontype ] = tmp;
5178					}
5179				}
5180			}
5181		}
5182
5183		return event.result;
5184	},
5185
5186	dispatch: function( event ) {
5187
5188		// Make a writable jQuery.Event from the native event object
5189		event = jQuery.event.fix( event );
5190
5191		var i, j, ret, matched, handleObj,
5192			handlerQueue = [],
5193			args = slice.call( arguments ),
5194			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
5195			special = jQuery.event.special[ event.type ] || {};
5196
5197		// Use the fix-ed jQuery.Event rather than the (read-only) native event
5198		args[ 0 ] = event;
5199		event.delegateTarget = this;
5200
5201		// Call the preDispatch hook for the mapped type, and let it bail if desired
5202		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
5203			return;
5204		}
5205
5206		// Determine handlers
5207		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
5208
5209		// Run delegates first; they may want to stop propagation beneath us
5210		i = 0;
5211		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
5212			event.currentTarget = matched.elem;
5213
5214			j = 0;
5215			while ( ( handleObj = matched.handlers[ j++ ] ) &&
5216				!event.isImmediatePropagationStopped() ) {
5217
5218				// Triggered event must either 1) have no namespace, or 2) have namespace(s)
5219				// a subset or equal to those in the bound event (both can have no namespace).
5220				if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
5221
5222					event.handleObj = handleObj;
5223					event.data = handleObj.data;
5224
5225					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
5226						handleObj.handler ).apply( matched.elem, args );
5227
5228					if ( ret !== undefined ) {
5229						if ( ( event.result = ret ) === false ) {
5230							event.preventDefault();
5231							event.stopPropagation();
5232						}
5233					}
5234				}
5235			}
5236		}
5237
5238		// Call the postDispatch hook for the mapped type
5239		if ( special.postDispatch ) {
5240			special.postDispatch.call( this, event );
5241		}
5242
5243		return event.result;
5244	},
5245
5246	handlers: function( event, handlers ) {
5247		var i, matches, sel, handleObj,
5248			handlerQueue = [],
5249			delegateCount = handlers.delegateCount,
5250			cur = event.target;
5251
5252		// Support (at least): Chrome, IE9
5253		// Find delegate handlers
5254		// Black-hole SVG <use> instance trees (#13180)
5255		//
5256		// Support: Firefox<=42+
5257		// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
5258		if ( delegateCount && cur.nodeType &&
5259			( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {
5260
5261			/* jshint eqeqeq: false */
5262			for ( ; cur != this; cur = cur.parentNode || this ) {
5263				/* jshint eqeqeq: true */
5264
5265				// Don't check non-elements (#13208)
5266				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
5267				if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
5268					matches = [];
5269					for ( i = 0; i < delegateCount; i++ ) {
5270						handleObj = handlers[ i ];
5271
5272						// Don't conflict with Object.prototype properties (#13203)
5273						sel = handleObj.selector + " ";
5274
5275						if ( matches[ sel ] === undefined ) {
5276							matches[ sel ] = handleObj.needsContext ?
5277								jQuery( sel, this ).index( cur ) > -1 :
5278								jQuery.find( sel, this, null, [ cur ] ).length;
5279						}
5280						if ( matches[ sel ] ) {
5281							matches.push( handleObj );
5282						}
5283					}
5284					if ( matches.length ) {
5285						handlerQueue.push( { elem: cur, handlers: matches } );
5286					}
5287				}
5288			}
5289		}
5290
5291		// Add the remaining (directly-bound) handlers
5292		if ( delegateCount < handlers.length ) {
5293			handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
5294		}
5295
5296		return handlerQueue;
5297	},
5298
5299	fix: function( event ) {
5300		if ( event[ jQuery.expando ] ) {
5301			return event;
5302		}
5303
5304		// Create a writable copy of the event object and normalize some properties
5305		var i, prop, copy,
5306			type = event.type,
5307			originalEvent = event,
5308			fixHook = this.fixHooks[ type ];
5309
5310		if ( !fixHook ) {
5311			this.fixHooks[ type ] = fixHook =
5312				rmouseEvent.test( type ) ? this.mouseHooks :
5313				rkeyEvent.test( type ) ? this.keyHooks :
5314				{};
5315		}
5316		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
5317
5318		event = new jQuery.Event( originalEvent );
5319
5320		i = copy.length;
5321		while ( i-- ) {
5322			prop = copy[ i ];
5323			event[ prop ] = originalEvent[ prop ];
5324		}
5325
5326		// Support: IE<9
5327		// Fix target property (#1925)
5328		if ( !event.target ) {
5329			event.target = originalEvent.srcElement || document;
5330		}
5331
5332		// Support: Safari 6-8+
5333		// Target should not be a text node (#504, #13143)
5334		if ( event.target.nodeType === 3 ) {
5335			event.target = event.target.parentNode;
5336		}
5337
5338		// Support: IE<9
5339		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
5340		event.metaKey = !!event.metaKey;
5341
5342		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
5343	},
5344
5345	// Includes some event props shared by KeyEvent and MouseEvent
5346	props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
5347		"metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ),
5348
5349	fixHooks: {},
5350
5351	keyHooks: {
5352		props: "char charCode key keyCode".split( " " ),
5353		filter: function( event, original ) {
5354
5355			// Add which for key events
5356			if ( event.which == null ) {
5357				event.which = original.charCode != null ? original.charCode : original.keyCode;
5358			}
5359
5360			return event;
5361		}
5362	},
5363
5364	mouseHooks: {
5365		props: ( "button buttons clientX clientY fromElement offsetX offsetY " +
5366			"pageX pageY screenX screenY toElement" ).split( " " ),
5367		filter: function( event, original ) {
5368			var body, eventDoc, doc,
5369				button = original.button,
5370				fromElement = original.fromElement;
5371
5372			// Calculate pageX/Y if missing and clientX/Y available
5373			if ( event.pageX == null && original.clientX != null ) {
5374				eventDoc = event.target.ownerDocument || document;
5375				doc = eventDoc.documentElement;
5376				body = eventDoc.body;
5377
5378				event.pageX = original.clientX +
5379					( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
5380					( doc && doc.clientLeft || body && body.clientLeft || 0 );
5381				event.pageY = original.clientY +
5382					( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
5383					( doc && doc.clientTop  || body && body.clientTop  || 0 );
5384			}
5385
5386			// Add relatedTarget, if necessary
5387			if ( !event.relatedTarget && fromElement ) {
5388				event.relatedTarget = fromElement === event.target ?
5389					original.toElement :
5390					fromElement;
5391			}
5392
5393			// Add which for click: 1 === left; 2 === middle; 3 === right
5394			// Note: button is not normalized, so don't use it
5395			if ( !event.which && button !== undefined ) {
5396				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
5397			}
5398
5399			return event;
5400		}
5401	},
5402
5403	special: {
5404		load: {
5405
5406			// Prevent triggered image.load events from bubbling to window.load
5407			noBubble: true
5408		},
5409		focus: {
5410
5411			// Fire native event if possible so blur/focus sequence is correct
5412			trigger: function() {
5413				if ( this !== safeActiveElement() && this.focus ) {
5414					try {
5415						this.focus();
5416						return false;
5417					} catch ( e ) {
5418
5419						// Support: IE<9
5420						// If we error on focus to hidden element (#1486, #12518),
5421						// let .trigger() run the handlers
5422					}
5423				}
5424			},
5425			delegateType: "focusin"
5426		},
5427		blur: {
5428			trigger: function() {
5429				if ( this === safeActiveElement() && this.blur ) {
5430					this.blur();
5431					return false;
5432				}
5433			},
5434			delegateType: "focusout"
5435		},
5436		click: {
5437
5438			// For checkbox, fire native event so checked state will be right
5439			trigger: function() {
5440				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
5441					this.click();
5442					return false;
5443				}
5444			},
5445
5446			// For cross-browser consistency, don't fire native .click() on links
5447			_default: function( event ) {
5448				return jQuery.nodeName( event.target, "a" );
5449			}
5450		},
5451
5452		beforeunload: {
5453			postDispatch: function( event ) {
5454
5455				// Support: Firefox 20+
5456				// Firefox doesn't alert if the returnValue field is not set.
5457				if ( event.result !== undefined && event.originalEvent ) {
5458					event.originalEvent.returnValue = event.result;
5459				}
5460			}
5461		}
5462	},
5463
5464	// Piggyback on a donor event to simulate a different one
5465	simulate: function( type, elem, event ) {
5466		var e = jQuery.extend(
5467			new jQuery.Event(),
5468			event,
5469			{
5470				type: type,
5471				isSimulated: true
5472
5473				// Previously, `originalEvent: {}` was set here, so stopPropagation call
5474				// would not be triggered on donor event, since in our own
5475				// jQuery.event.stopPropagation function we had a check for existence of
5476				// originalEvent.stopPropagation method, so, consequently it would be a noop.
5477				//
5478				// Guard for simulated events was moved to jQuery.event.stopPropagation function
5479				// since `originalEvent` should point to the original event for the
5480				// constancy with other events and for more focused logic
5481			}
5482		);
5483
5484		jQuery.event.trigger( e, null, elem );
5485
5486		if ( e.isDefaultPrevented() ) {
5487			event.preventDefault();
5488		}
5489	}
5490};
5491
5492jQuery.removeEvent = document.removeEventListener ?
5493	function( elem, type, handle ) {
5494
5495		// This "if" is needed for plain objects
5496		if ( elem.removeEventListener ) {
5497			elem.removeEventListener( type, handle );
5498		}
5499	} :
5500	function( elem, type, handle ) {
5501		var name = "on" + type;
5502
5503		if ( elem.detachEvent ) {
5504
5505			// #8545, #7054, preventing memory leaks for custom events in IE6-8
5506			// detachEvent needed property on element, by name of that event,
5507			// to properly expose it to GC
5508			if ( typeof elem[ name ] === "undefined" ) {
5509				elem[ name ] = null;
5510			}
5511
5512			elem.detachEvent( name, handle );
5513		}
5514	};
5515
5516jQuery.Event = function( src, props ) {
5517
5518	// Allow instantiation without the 'new' keyword
5519	if ( !( this instanceof jQuery.Event ) ) {
5520		return new jQuery.Event( src, props );
5521	}
5522
5523	// Event object
5524	if ( src && src.type ) {
5525		this.originalEvent = src;
5526		this.type = src.type;
5527
5528		// Events bubbling up the document may have been marked as prevented
5529		// by a handler lower down the tree; reflect the correct value.
5530		this.isDefaultPrevented = src.defaultPrevented ||
5531				src.defaultPrevented === undefined &&
5532
5533				// Support: IE < 9, Android < 4.0
5534				src.returnValue === false ?
5535			returnTrue :
5536			returnFalse;
5537
5538	// Event type
5539	} else {
5540		this.type = src;
5541	}
5542
5543	// Put explicitly provided properties onto the event object
5544	if ( props ) {
5545		jQuery.extend( this, props );
5546	}
5547
5548	// Create a timestamp if incoming event doesn't have one
5549	this.timeStamp = src && src.timeStamp || jQuery.now();
5550
5551	// Mark it as fixed
5552	this[ jQuery.expando ] = true;
5553};
5554
5555// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
5556// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
5557jQuery.Event.prototype = {
5558	constructor: jQuery.Event,
5559	isDefaultPrevented: returnFalse,
5560	isPropagationStopped: returnFalse,
5561	isImmediatePropagationStopped: returnFalse,
5562
5563	preventDefault: function() {
5564		var e = this.originalEvent;
5565
5566		this.isDefaultPrevented = returnTrue;
5567		if ( !e ) {
5568			return;
5569		}
5570
5571		// If preventDefault exists, run it on the original event
5572		if ( e.preventDefault ) {
5573			e.preventDefault();
5574
5575		// Support: IE
5576		// Otherwise set the returnValue property of the original event to false
5577		} else {
5578			e.returnValue = false;
5579		}
5580	},
5581	stopPropagation: function() {
5582		var e = this.originalEvent;
5583
5584		this.isPropagationStopped = returnTrue;
5585
5586		if ( !e || this.isSimulated ) {
5587			return;
5588		}
5589
5590		// If stopPropagation exists, run it on the original event
5591		if ( e.stopPropagation ) {
5592			e.stopPropagation();
5593		}
5594
5595		// Support: IE
5596		// Set the cancelBubble property of the original event to true
5597		e.cancelBubble = true;
5598	},
5599	stopImmediatePropagation: function() {
5600		var e = this.originalEvent;
5601
5602		this.isImmediatePropagationStopped = returnTrue;
5603
5604		if ( e && e.stopImmediatePropagation ) {
5605			e.stopImmediatePropagation();
5606		}
5607
5608		this.stopPropagation();
5609	}
5610};
5611
5612// Create mouseenter/leave events using mouseover/out and event-time checks
5613// so that event delegation works in jQuery.
5614// Do the same for pointerenter/pointerleave and pointerover/pointerout
5615//
5616// Support: Safari 7 only
5617// Safari sends mouseenter too often; see:
5618// https://code.google.com/p/chromium/issues/detail?id=470258
5619// for the description of the bug (it existed in older Chrome versions as well).
5620jQuery.each( {
5621	mouseenter: "mouseover",
5622	mouseleave: "mouseout",
5623	pointerenter: "pointerover",
5624	pointerleave: "pointerout"
5625}, function( orig, fix ) {
5626	jQuery.event.special[ orig ] = {
5627		delegateType: fix,
5628		bindType: fix,
5629
5630		handle: function( event ) {
5631			var ret,
5632				target = this,
5633				related = event.relatedTarget,
5634				handleObj = event.handleObj;
5635
5636			// For mouseenter/leave call the handler if related is outside the target.
5637			// NB: No relatedTarget if the mouse left/entered the browser window
5638			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
5639				event.type = handleObj.origType;
5640				ret = handleObj.handler.apply( this, arguments );
5641				event.type = fix;
5642			}
5643			return ret;
5644		}
5645	};
5646} );
5647
5648// IE submit delegation
5649if ( !support.submit ) {
5650
5651	jQuery.event.special.submit = {
5652		setup: function() {
5653
5654			// Only need this for delegated form submit events
5655			if ( jQuery.nodeName( this, "form" ) ) {
5656				return false;
5657			}
5658
5659			// Lazy-add a submit handler when a descendant form may potentially be submitted
5660			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
5661
5662				// Node name check avoids a VML-related crash in IE (#9807)
5663				var elem = e.target,
5664					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ?
5665
5666						// Support: IE <=8
5667						// We use jQuery.prop instead of elem.form
5668						// to allow fixing the IE8 delegated submit issue (gh-2332)
5669						// by 3rd party polyfills/workarounds.
5670						jQuery.prop( elem, "form" ) :
5671						undefined;
5672
5673				if ( form && !jQuery._data( form, "submit" ) ) {
5674					jQuery.event.add( form, "submit._submit", function( event ) {
5675						event._submitBubble = true;
5676					} );
5677					jQuery._data( form, "submit", true );
5678				}
5679			} );
5680
5681			// return undefined since we don't need an event listener
5682		},
5683
5684		postDispatch: function( event ) {
5685
5686			// If form was submitted by the user, bubble the event up the tree
5687			if ( event._submitBubble ) {
5688				delete event._submitBubble;
5689				if ( this.parentNode && !event.isTrigger ) {
5690					jQuery.event.simulate( "submit", this.parentNode, event );
5691				}
5692			}
5693		},
5694
5695		teardown: function() {
5696
5697			// Only need this for delegated form submit events
5698			if ( jQuery.nodeName( this, "form" ) ) {
5699				return false;
5700			}
5701
5702			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
5703			jQuery.event.remove( this, "._submit" );
5704		}
5705	};
5706}
5707
5708// IE change delegation and checkbox/radio fix
5709if ( !support.change ) {
5710
5711	jQuery.event.special.change = {
5712
5713		setup: function() {
5714
5715			if ( rformElems.test( this.nodeName ) ) {
5716
5717				// IE doesn't fire change on a check/radio until blur; trigger it on click
5718				// after a propertychange. Eat the blur-change in special.change.handle.
5719				// This still fires onchange a second time for check/radio after blur.
5720				if ( this.type === "checkbox" || this.type === "radio" ) {
5721					jQuery.event.add( this, "propertychange._change", function( event ) {
5722						if ( event.originalEvent.propertyName === "checked" ) {
5723							this._justChanged = true;
5724						}
5725					} );
5726					jQuery.event.add( this, "click._change", function( event ) {
5727						if ( this._justChanged && !event.isTrigger ) {
5728							this._justChanged = false;
5729						}
5730
5731						// Allow triggered, simulated change events (#11500)
5732						jQuery.event.simulate( "change", this, event );
5733					} );
5734				}
5735				return false;
5736			}
5737
5738			// Delegated event; lazy-add a change handler on descendant inputs
5739			jQuery.event.add( this, "beforeactivate._change", function( e ) {
5740				var elem = e.target;
5741
5742				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "change" ) ) {
5743					jQuery.event.add( elem, "change._change", function( event ) {
5744						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
5745							jQuery.event.simulate( "change", this.parentNode, event );
5746						}
5747					} );
5748					jQuery._data( elem, "change", true );
5749				}
5750			} );
5751		},
5752
5753		handle: function( event ) {
5754			var elem = event.target;
5755
5756			// Swallow native change events from checkbox/radio, we already triggered them above
5757			if ( this !== elem || event.isSimulated || event.isTrigger ||
5758				( elem.type !== "radio" && elem.type !== "checkbox" ) ) {
5759
5760				return event.handleObj.handler.apply( this, arguments );
5761			}
5762		},
5763
5764		teardown: function() {
5765			jQuery.event.remove( this, "._change" );
5766
5767			return !rformElems.test( this.nodeName );
5768		}
5769	};
5770}
5771
5772// Support: Firefox
5773// Firefox doesn't have focus(in | out) events
5774// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
5775//
5776// Support: Chrome, Safari
5777// focus(in | out) events fire after focus & blur events,
5778// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
5779// Related ticket - https://code.google.com/p/chromium/issues/detail?id=449857
5780if ( !support.focusin ) {
5781	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
5782
5783		// Attach a single capturing handler on the document while someone wants focusin/focusout
5784		var handler = function( event ) {
5785			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
5786		};
5787
5788		jQuery.event.special[ fix ] = {
5789			setup: function() {
5790				var doc = this.ownerDocument || this,
5791					attaches = jQuery._data( doc, fix );
5792
5793				if ( !attaches ) {
5794					doc.addEventListener( orig, handler, true );
5795				}
5796				jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
5797			},
5798			teardown: function() {
5799				var doc = this.ownerDocument || this,
5800					attaches = jQuery._data( doc, fix ) - 1;
5801
5802				if ( !attaches ) {
5803					doc.removeEventListener( orig, handler, true );
5804					jQuery._removeData( doc, fix );
5805				} else {
5806					jQuery._data( doc, fix, attaches );
5807				}
5808			}
5809		};
5810	} );
5811}
5812
5813jQuery.fn.extend( {
5814
5815	on: function( types, selector, data, fn ) {
5816		return on( this, types, selector, data, fn );
5817	},
5818	one: function( types, selector, data, fn ) {
5819		return on( this, types, selector, data, fn, 1 );
5820	},
5821	off: function( types, selector, fn ) {
5822		var handleObj, type;
5823		if ( types && types.preventDefault && types.handleObj ) {
5824
5825			// ( event )  dispatched jQuery.Event
5826			handleObj = types.handleObj;
5827			jQuery( types.delegateTarget ).off(
5828				handleObj.namespace ?
5829					handleObj.origType + "." + handleObj.namespace :
5830					handleObj.origType,
5831				handleObj.selector,
5832				handleObj.handler
5833			);
5834			return this;
5835		}
5836		if ( typeof types === "object" ) {
5837
5838			// ( types-object [, selector] )
5839			for ( type in types ) {
5840				this.off( type, selector, types[ type ] );
5841			}
5842			return this;
5843		}
5844		if ( selector === false || typeof selector === "function" ) {
5845
5846			// ( types [, fn] )
5847			fn = selector;
5848			selector = undefined;
5849		}
5850		if ( fn === false ) {
5851			fn = returnFalse;
5852		}
5853		return this.each( function() {
5854			jQuery.event.remove( this, types, fn, selector );
5855		} );
5856	},
5857
5858	trigger: function( type, data ) {
5859		return this.each( function() {
5860			jQuery.event.trigger( type, data, this );
5861		} );
5862	},
5863	triggerHandler: function( type, data ) {
5864		var elem = this[ 0 ];
5865		if ( elem ) {
5866			return jQuery.event.trigger( type, data, elem, true );
5867		}
5868	}
5869} );
5870
5871
5872var rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
5873	rnoshimcache = new RegExp( "<(?:" + nodeNames + ")[\\s/>]", "i" ),
5874	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
5875
5876	// Support: IE 10-11, Edge 10240+
5877	// In IE/Edge using regex groups here causes severe slowdowns.
5878	// See https://connect.microsoft.com/IE/feedback/details/1736512/
5879	rnoInnerhtml = /<script|<style|<link/i,
5880
5881	// checked="checked" or checked
5882	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
5883	rscriptTypeMasked = /^true\/(.*)/,
5884	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
5885	safeFragment = createSafeFragment( document ),
5886	fragmentDiv = safeFragment.appendChild( document.createElement( "div" ) );
5887
5888// Support: IE<8
5889// Manipulating tables requires a tbody
5890function manipulationTarget( elem, content ) {
5891	return jQuery.nodeName( elem, "table" ) &&
5892		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
5893
5894		elem.getElementsByTagName( "tbody" )[ 0 ] ||
5895			elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) :
5896		elem;
5897}
5898
5899// Replace/restore the type attribute of script elements for safe DOM manipulation
5900function disableScript( elem ) {
5901	elem.type = ( jQuery.find.attr( elem, "type" ) !== null ) + "/" + elem.type;
5902	return elem;
5903}
5904function restoreScript( elem ) {
5905	var match = rscriptTypeMasked.exec( elem.type );
5906	if ( match ) {
5907		elem.type = match[ 1 ];
5908	} else {
5909		elem.removeAttribute( "type" );
5910	}
5911	return elem;
5912}
5913
5914function cloneCopyEvent( src, dest ) {
5915	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
5916		return;
5917	}
5918
5919	var type, i, l,
5920		oldData = jQuery._data( src ),
5921		curData = jQuery._data( dest, oldData ),
5922		events = oldData.events;
5923
5924	if ( events ) {
5925		delete curData.handle;
5926		curData.events = {};
5927
5928		for ( type in events ) {
5929			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
5930				jQuery.event.add( dest, type, events[ type ][ i ] );
5931			}
5932		}
5933	}
5934
5935	// make the cloned public data object a copy from the original
5936	if ( curData.data ) {
5937		curData.data = jQuery.extend( {}, curData.data );
5938	}
5939}
5940
5941function fixCloneNodeIssues( src, dest ) {
5942	var nodeName, e, data;
5943
5944	// We do not need to do anything for non-Elements
5945	if ( dest.nodeType !== 1 ) {
5946		return;
5947	}
5948
5949	nodeName = dest.nodeName.toLowerCase();
5950
5951	// IE6-8 copies events bound via attachEvent when using cloneNode.
5952	if ( !support.noCloneEvent && dest[ jQuery.expando ] ) {
5953		data = jQuery._data( dest );
5954
5955		for ( e in data.events ) {
5956			jQuery.removeEvent( dest, e, data.handle );
5957		}
5958
5959		// Event data gets referenced instead of copied if the expando gets copied too
5960		dest.removeAttribute( jQuery.expando );
5961	}
5962
5963	// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
5964	if ( nodeName === "script" && dest.text !== src.text ) {
5965		disableScript( dest ).text = src.text;
5966		restoreScript( dest );
5967
5968	// IE6-10 improperly clones children of object elements using classid.
5969	// IE10 throws NoModificationAllowedError if parent is null, #12132.
5970	} else if ( nodeName === "object" ) {
5971		if ( dest.parentNode ) {
5972			dest.outerHTML = src.outerHTML;
5973		}
5974
5975		// This path appears unavoidable for IE9. When cloning an object
5976		// element in IE9, the outerHTML strategy above is not sufficient.
5977		// If the src has innerHTML and the destination does not,
5978		// copy the src.innerHTML into the dest.innerHTML. #10324
5979		if ( support.html5Clone && ( src.innerHTML && !jQuery.trim( dest.innerHTML ) ) ) {
5980			dest.innerHTML = src.innerHTML;
5981		}
5982
5983	} else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
5984
5985		// IE6-8 fails to persist the checked state of a cloned checkbox
5986		// or radio button. Worse, IE6-7 fail to give the cloned element
5987		// a checked appearance if the defaultChecked value isn't also set
5988
5989		dest.defaultChecked = dest.checked = src.checked;
5990
5991		// IE6-7 get confused and end up setting the value of a cloned
5992		// checkbox/radio button to an empty string instead of "on"
5993		if ( dest.value !== src.value ) {
5994			dest.value = src.value;
5995		}
5996
5997	// IE6-8 fails to return the selected option to the default selected
5998	// state when cloning options
5999	} else if ( nodeName === "option" ) {
6000		dest.defaultSelected = dest.selected = src.defaultSelected;
6001
6002	// IE6-8 fails to set the defaultValue to the correct value when
6003	// cloning other types of input fields
6004	} else if ( nodeName === "input" || nodeName === "textarea" ) {
6005		dest.defaultValue = src.defaultValue;
6006	}
6007}
6008
6009function domManip( collection, args, callback, ignored ) {
6010
6011	// Flatten any nested arrays
6012	args = concat.apply( [], args );
6013
6014	var first, node, hasScripts,
6015		scripts, doc, fragment,
6016		i = 0,
6017		l = collection.length,
6018		iNoClone = l - 1,
6019		value = args[ 0 ],
6020		isFunction = jQuery.isFunction( value );
6021
6022	// We can't cloneNode fragments that contain checked, in WebKit
6023	if ( isFunction ||
6024			( l > 1 && typeof value === "string" &&
6025				!support.checkClone && rchecked.test( value ) ) ) {
6026		return collection.each( function( index ) {
6027			var self = collection.eq( index );
6028			if ( isFunction ) {
6029				args[ 0 ] = value.call( this, index, self.html() );
6030			}
6031			domManip( self, args, callback, ignored );
6032		} );
6033	}
6034
6035	if ( l ) {
6036		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
6037		first = fragment.firstChild;
6038
6039		if ( fragment.childNodes.length === 1 ) {
6040			fragment = first;
6041		}
6042
6043		// Require either new content or an interest in ignored elements to invoke the callback
6044		if ( first || ignored ) {
6045			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
6046			hasScripts = scripts.length;
6047
6048			// Use the original fragment for the last item
6049			// instead of the first because it can end up
6050			// being emptied incorrectly in certain situations (#8070).
6051			for ( ; i < l; i++ ) {
6052				node = fragment;
6053
6054				if ( i !== iNoClone ) {
6055					node = jQuery.clone( node, true, true );
6056
6057					// Keep references to cloned scripts for later restoration
6058					if ( hasScripts ) {
6059
6060						// Support: Android<4.1, PhantomJS<2
6061						// push.apply(_, arraylike) throws on ancient WebKit
6062						jQuery.merge( scripts, getAll( node, "script" ) );
6063					}
6064				}
6065
6066				callback.call( collection[ i ], node, i );
6067			}
6068
6069			if ( hasScripts ) {
6070				doc = scripts[ scripts.length - 1 ].ownerDocument;
6071
6072				// Reenable scripts
6073				jQuery.map( scripts, restoreScript );
6074
6075				// Evaluate executable scripts on first document insertion
6076				for ( i = 0; i < hasScripts; i++ ) {
6077					node = scripts[ i ];
6078					if ( rscriptType.test( node.type || "" ) &&
6079						!jQuery._data( node, "globalEval" ) &&
6080						jQuery.contains( doc, node ) ) {
6081
6082						if ( node.src ) {
6083
6084							// Optional AJAX dependency, but won't run scripts if not present
6085							if ( jQuery._evalUrl ) {
6086								jQuery._evalUrl( node.src );
6087							}
6088						} else {
6089							jQuery.globalEval(
6090								( node.text || node.textContent || node.innerHTML || "" )
6091									.replace( rcleanScript, "" )
6092							);
6093						}
6094					}
6095				}
6096			}
6097
6098			// Fix #11809: Avoid leaking memory
6099			fragment = first = null;
6100		}
6101	}
6102
6103	return collection;
6104}
6105
6106function remove( elem, selector, keepData ) {
6107	var node,
6108		elems = selector ? jQuery.filter( selector, elem ) : elem,
6109		i = 0;
6110
6111	for ( ; ( node = elems[ i ] ) != null; i++ ) {
6112
6113		if ( !keepData && node.nodeType === 1 ) {
6114			jQuery.cleanData( getAll( node ) );
6115		}
6116
6117		if ( node.parentNode ) {
6118			if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
6119				setGlobalEval( getAll( node, "script" ) );
6120			}
6121			node.parentNode.removeChild( node );
6122		}
6123	}
6124
6125	return elem;
6126}
6127
6128jQuery.extend( {
6129	htmlPrefilter: function( html ) {
6130		return html.replace( rxhtmlTag, "<$1></$2>" );
6131	},
6132
6133	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
6134		var destElements, node, clone, i, srcElements,
6135			inPage = jQuery.contains( elem.ownerDocument, elem );
6136
6137		if ( support.html5Clone || jQuery.isXMLDoc( elem ) ||
6138			!rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
6139
6140			clone = elem.cloneNode( true );
6141
6142		// IE<=8 does not properly clone detached, unknown element nodes
6143		} else {
6144			fragmentDiv.innerHTML = elem.outerHTML;
6145			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
6146		}
6147
6148		if ( ( !support.noCloneEvent || !support.noCloneChecked ) &&
6149				( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) {
6150
6151			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
6152			destElements = getAll( clone );
6153			srcElements = getAll( elem );
6154
6155			// Fix all IE cloning issues
6156			for ( i = 0; ( node = srcElements[ i ] ) != null; ++i ) {
6157
6158				// Ensure that the destination node is not null; Fixes #9587
6159				if ( destElements[ i ] ) {
6160					fixCloneNodeIssues( node, destElements[ i ] );
6161				}
6162			}
6163		}
6164
6165		// Copy the events from the original to the clone
6166		if ( dataAndEvents ) {
6167			if ( deepDataAndEvents ) {
6168				srcElements = srcElements || getAll( elem );
6169				destElements = destElements || getAll( clone );
6170
6171				for ( i = 0; ( node = srcElements[ i ] ) != null; i++ ) {
6172					cloneCopyEvent( node, destElements[ i ] );
6173				}
6174			} else {
6175				cloneCopyEvent( elem, clone );
6176			}
6177		}
6178
6179		// Preserve script evaluation history
6180		destElements = getAll( clone, "script" );
6181		if ( destElements.length > 0 ) {
6182			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
6183		}
6184
6185		destElements = srcElements = node = null;
6186
6187		// Return the cloned set
6188		return clone;
6189	},
6190
6191	cleanData: function( elems, /* internal */ forceAcceptData ) {
6192		var elem, type, id, data,
6193			i = 0,
6194			internalKey = jQuery.expando,
6195			cache = jQuery.cache,
6196			attributes = support.attributes,
6197			special = jQuery.event.special;
6198
6199		for ( ; ( elem = elems[ i ] ) != null; i++ ) {
6200			if ( forceAcceptData || acceptData( elem ) ) {
6201
6202				id = elem[ internalKey ];
6203				data = id && cache[ id ];
6204
6205				if ( data ) {
6206					if ( data.events ) {
6207						for ( type in data.events ) {
6208							if ( special[ type ] ) {
6209								jQuery.event.remove( elem, type );
6210
6211							// This is a shortcut to avoid jQuery.event.remove's overhead
6212							} else {
6213								jQuery.removeEvent( elem, type, data.handle );
6214							}
6215						}
6216					}
6217
6218					// Remove cache only if it was not already removed by jQuery.event.remove
6219					if ( cache[ id ] ) {
6220
6221						delete cache[ id ];
6222
6223						// Support: IE<9
6224						// IE does not allow us to delete expando properties from nodes
6225						// IE creates expando attributes along with the property
6226						// IE does not have a removeAttribute function on Document nodes
6227						if ( !attributes && typeof elem.removeAttribute !== "undefined" ) {
6228							elem.removeAttribute( internalKey );
6229
6230						// Webkit & Blink performance suffers when deleting properties
6231						// from DOM nodes, so set to undefined instead
6232						// https://code.google.com/p/chromium/issues/detail?id=378607
6233						} else {
6234							elem[ internalKey ] = undefined;
6235						}
6236
6237						deletedIds.push( id );
6238					}
6239				}
6240			}
6241		}
6242	}
6243} );
6244
6245jQuery.fn.extend( {
6246
6247	// Keep domManip exposed until 3.0 (gh-2225)
6248	domManip: domManip,
6249
6250	detach: function( selector ) {
6251		return remove( this, selector, true );
6252	},
6253
6254	remove: function( selector ) {
6255		return remove( this, selector );
6256	},
6257
6258	text: function( value ) {
6259		return access( this, function( value ) {
6260			return value === undefined ?
6261				jQuery.text( this ) :
6262				this.empty().append(
6263					( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( value )
6264				);
6265		}, null, value, arguments.length );
6266	},
6267
6268	append: function() {
6269		return domManip( this, arguments, function( elem ) {
6270			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
6271				var target = manipulationTarget( this, elem );
6272				target.appendChild( elem );
6273			}
6274		} );
6275	},
6276
vendor: 4,288 bytes, lines 6277-6450
6277	prepend: function() {
6278		return domManip( this, arguments, function( elem ) {
6279			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
6280				var target = manipulationTarget( this, elem );
6281				target.insertBefore( elem, target.firstChild );
6282			}
6283		} );
6284	},
6285
6286	before: function() {
6287		return domManip( this, arguments, function( elem ) {
6288			if ( this.parentNode ) {
6289				this.parentNode.insertBefore( elem, this );
6290			}
6291		} );
6292	},
6293
6294	after: function() {
6295		return domManip( this, arguments, function( elem ) {
6296			if ( this.parentNode ) {
6297				this.parentNode.insertBefore( elem, this.nextSibling );
6298			}
6299		} );
6300	},
6301
6302	empty: function() {
6303		var elem,
6304			i = 0;
6305
6306		for ( ; ( elem = this[ i ] ) != null; i++ ) {
6307
6308			// Remove element nodes and prevent memory leaks
6309			if ( elem.nodeType === 1 ) {
6310				jQuery.cleanData( getAll( elem, false ) );
6311			}
6312
6313			// Remove any remaining nodes
6314			while ( elem.firstChild ) {
6315				elem.removeChild( elem.firstChild );
6316			}
6317
6318			// If this is a select, ensure that it displays empty (#12336)
6319			// Support: IE<9
6320			if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
6321				elem.options.length = 0;
6322			}
6323		}
6324
6325		return this;
6326	},
6327
6328	clone: function( dataAndEvents, deepDataAndEvents ) {
6329		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
6330		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
6331
6332		return this.map( function() {
6333			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
6334		} );
6335	},
6336
6337	html: function( value ) {
6338		return access( this, function( value ) {
6339			var elem = this[ 0 ] || {},
6340				i = 0,
6341				l = this.length;
6342
6343			if ( value === undefined ) {
6344				return elem.nodeType === 1 ?
6345					elem.innerHTML.replace( rinlinejQuery, "" ) :
6346					undefined;
6347			}
6348
6349			// See if we can take a shortcut and just use innerHTML
6350			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
6351				( support.htmlSerialize || !rnoshimcache.test( value )  ) &&
6352				( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
6353				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
6354
6355				value = jQuery.htmlPrefilter( value );
6356
6357				try {
6358					for ( ; i < l; i++ ) {
6359
6360						// Remove element nodes and prevent memory leaks
6361						elem = this[ i ] || {};
6362						if ( elem.nodeType === 1 ) {
6363							jQuery.cleanData( getAll( elem, false ) );
6364							elem.innerHTML = value;
6365						}
6366					}
6367
6368					elem = 0;
6369
6370				// If using innerHTML throws an exception, use the fallback method
6371				} catch ( e ) {}
6372			}
6373
6374			if ( elem ) {
6375				this.empty().append( value );
6376			}
6377		}, null, value, arguments.length );
6378	},
6379
6380	replaceWith: function() {
6381		var ignored = [];
6382
6383		// Make the changes, replacing each non-ignored context element with the new content
6384		return domManip( this, arguments, function( elem ) {
6385			var parent = this.parentNode;
6386
6387			if ( jQuery.inArray( this, ignored ) < 0 ) {
6388				jQuery.cleanData( getAll( this ) );
6389				if ( parent ) {
6390					parent.replaceChild( elem, this );
6391				}
6392			}
6393
6394		// Force callback invocation
6395		}, ignored );
6396	}
6397} );
6398
6399jQuery.each( {
6400	appendTo: "append",
6401	prependTo: "prepend",
6402	insertBefore: "before",
6403	insertAfter: "after",
6404	replaceAll: "replaceWith"
6405}, function( name, original ) {
6406	jQuery.fn[ name ] = function( selector ) {
6407		var elems,
6408			i = 0,
6409			ret = [],
6410			insert = jQuery( selector ),
6411			last = insert.length - 1;
6412
6413		for ( ; i <= last; i++ ) {
6414			elems = i === last ? this : this.clone( true );
6415			jQuery( insert[ i ] )[ original ]( elems );
6416
6417			// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
6418			push.apply( ret, elems.get() );
6419		}
6420
6421		return this.pushStack( ret );
6422	};
6423} );
6424
6425
6426var iframe,
6427	elemdisplay = {
6428
6429		// Support: Firefox
6430		// We have to pre-define these values for FF (#10227)
6431		HTML: "block",
6432		BODY: "block"
6433	};
6434
6435/**
6436 * Retrieve the actual display of a element
6437 * @param {String} name nodeName of the element
6438 * @param {Object} doc Document object
6439 */
6440
6441// Called only from within defaultDisplay
6442function actualDisplay( name, doc ) {
6443	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
6444
6445		display = jQuery.css( elem[ 0 ], "display" );
6446
6447	// We don't have any data stored on the element,
6448	// so use "detach" method as fast way to get rid of the element
6449	elem.detach();
6450
6451	return display;
6452}
6453
6454/**
6455 * Try to determine the default display value of an element
6456 * @param {String} nodeName
6457 */
6458function defaultDisplay( nodeName ) {
6459	var doc = document,
6460		display = elemdisplay[ nodeName ];
6461
6462	if ( !display ) {
6463		display = actualDisplay( nodeName, doc );
6464
6465		// If the simple way fails, read from inside an iframe
6466		if ( display === "none" || !display ) {
6467
6468			// Use the already-created iframe if possible
6469			iframe = ( iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" ) )
6470				.appendTo( doc.documentElement );
6471
6472			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
6473			doc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;
6474
6475			// Support: IE
6476			doc.write();
6477			doc.close();
6478
6479			display = actualDisplay( nodeName, doc );
6480			iframe.detach();
6481		}
6482
6483		// Store the correct default display
6484		elemdisplay[ nodeName ] = display;
6485	}
6486
6487	return display;
6488}
6489var rmargin = ( /^margin/ );
6490
6491var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
6492
6493var swap = function( elem, options, callback, args ) {
6494	var ret, name,
6495		old = {};
6496
6497	// Remember the old values, and insert the new ones
6498	for ( name in options ) {
6499		old[ name ] = elem.style[ name ];
6500		elem.style[ name ] = options[ name ];
6501	}
6502
6503	ret = callback.apply( elem, args || [] );
6504
6505	// Revert the old values
6506	for ( name in options ) {
6507		elem.style[ name ] = old[ name ];
6508	}
6509
6510	return ret;
6511};
6512
6513
6514var documentElement = document.documentElement;
6515
6516
6517
6518( function() {
6519	var pixelPositionVal, pixelMarginRightVal, boxSizingReliableVal,
6520		reliableHiddenOffsetsVal, reliableMarginRightVal, reliableMarginLeftVal,
6521		container = document.createElement( "div" ),
6522		div = document.createElement( "div" );
6523
6524	// Finish early in limited (non-browser) environments
6525	if ( !div.style ) {
6526		return;
6527	}
6528
6529	div.style.cssText = "float:left;opacity:.5";
6530
6531	// Support: IE<9
6532	// Make sure that element opacity exists (as opposed to filter)
6533	support.opacity = div.style.opacity === "0.5";
6534
6535	// Verify style float existence
6536	// (IE uses styleFloat instead of cssFloat)
6537	support.cssFloat = !!div.style.cssFloat;
6538
6539	div.style.backgroundClip = "content-box";
6540	div.cloneNode( true ).style.backgroundClip = "";
6541	support.clearCloneStyle = div.style.backgroundClip === "content-box";
6542
6543	container = document.createElement( "div" );
6544	container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
6545		"padding:0;margin-top:1px;position:absolute";
6546	div.innerHTML = "";
6547	container.appendChild( div );
6548
6549	// Support: Firefox<29, Android 2.3
6550	// Vendor-prefix box-sizing
6551	support.boxSizing = div.style.boxSizing === "" || div.style.MozBoxSizing === "" ||
6552		div.style.WebkitBoxSizing === "";
6553
6554	jQuery.extend( support, {
6555		reliableHiddenOffsets: function() {
6556			if ( pixelPositionVal == null ) {
6557				computeStyleTests();
6558			}
6559			return reliableHiddenOffsetsVal;
6560		},
6561
6562		boxSizingReliable: function() {
6563
6564			// We're checking for pixelPositionVal here instead of boxSizingReliableVal
6565			// since that compresses better and they're computed together anyway.
6566			if ( pixelPositionVal == null ) {
6567				computeStyleTests();
6568			}
6569			return boxSizingReliableVal;
6570		},
6571
6572		pixelMarginRight: function() {
6573
6574			// Support: Android 4.0-4.3
6575			if ( pixelPositionVal == null ) {
6576				computeStyleTests();
6577			}
6578			return pixelMarginRightVal;
6579		},
6580
6581		pixelPosition: function() {
6582			if ( pixelPositionVal == null ) {
6583				computeStyleTests();
6584			}
6585			return pixelPositionVal;
6586		},
6587
6588		reliableMarginRight: function() {
6589
6590			// Support: Android 2.3
6591			if ( pixelPositionVal == null ) {
6592				computeStyleTests();
6593			}
6594			return reliableMarginRightVal;
6595		},
6596
6597		reliableMarginLeft: function() {
6598
6599			// Support: IE <=8 only, Android 4.0 - 4.3 only, Firefox <=3 - 37
6600			if ( pixelPositionVal == null ) {
6601				computeStyleTests();
6602			}
6603			return reliableMarginLeftVal;
6604		}
6605	} );
6606
6607	function computeStyleTests() {
6608		var contents, divStyle,
6609			documentElement = document.documentElement;
6610
6611		// Setup
6612		documentElement.appendChild( container );
6613
6614		div.style.cssText =
6615
6616			// Support: Android 2.3
6617			// Vendor-prefix box-sizing
6618			"-webkit-box-sizing:border-box;box-sizing:border-box;" +
6619			"position:relative;display:block;" +
6620			"margin:auto;border:1px;padding:1px;" +
6621			"top:1%;width:50%";
6622
6623		// Support: IE<9
6624		// Assume reasonable values in the absence of getComputedStyle
6625		pixelPositionVal = boxSizingReliableVal = reliableMarginLeftVal = false;
6626		pixelMarginRightVal = reliableMarginRightVal = true;
6627
6628		// Check for getComputedStyle so that this code is not run in IE<9.
6629		if ( window.getComputedStyle ) {
6630			divStyle = window.getComputedStyle( div );
6631			pixelPositionVal = ( divStyle || {} ).top !== "1%";
6632			reliableMarginLeftVal = ( divStyle || {} ).marginLeft === "2px";
6633			boxSizingReliableVal = ( divStyle || { width: "4px" } ).width === "4px";
6634
6635			// Support: Android 4.0 - 4.3 only
6636			// Some styles come back with percentage values, even though they shouldn't
6637			div.style.marginRight = "50%";
6638			pixelMarginRightVal = ( divStyle || { marginRight: "4px" } ).marginRight === "4px";
6639
6640			// Support: Android 2.3 only
6641			// Div with explicit width and no margin-right incorrectly
6642			// gets computed margin-right based on width of container (#3333)
6643			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
6644			contents = div.appendChild( document.createElement( "div" ) );
6645
6646			// Reset CSS: box-sizing; display; margin; border; padding
6647			contents.style.cssText = div.style.cssText =
6648
6649				// Support: Android 2.3
6650				// Vendor-prefix box-sizing
6651				"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
6652				"box-sizing:content-box;display:block;margin:0;border:0;padding:0";
6653			contents.style.marginRight = contents.style.width = "0";
6654			div.style.width = "1px";
6655
6656			reliableMarginRightVal =
6657				!parseFloat( ( window.getComputedStyle( contents ) || {} ).marginRight );
6658
6659			div.removeChild( contents );
6660		}
6661
6662		// Support: IE6-8
6663		// First check that getClientRects works as expected
6664		// Check if table cells still have offsetWidth/Height when they are set
6665		// to display:none and there are still other visible table cells in a
6666		// table row; if so, offsetWidth/Height are not reliable for use when
6667		// determining if an element has been hidden directly using
6668		// display:none (it is still safe to use offsets if a parent element is
6669		// hidden; don safety goggles and see bug #4512 for more information).
6670		div.style.display = "none";
6671		reliableHiddenOffsetsVal = div.getClientRects().length === 0;
6672		if ( reliableHiddenOffsetsVal ) {
6673			div.style.display = "";
6674			div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
6675			div.childNodes[ 0 ].style.borderCollapse = "separate";
6676			contents = div.getElementsByTagName( "td" );
6677			contents[ 0 ].style.cssText = "margin:0;border:0;padding:0;display:none";
6678			reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
6679			if ( reliableHiddenOffsetsVal ) {
6680				contents[ 0 ].style.display = "";
6681				contents[ 1 ].style.display = "none";
6682				reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
6683			}
6684		}
6685
6686		// Teardown
6687		documentElement.removeChild( container );
6688	}
6689
6690} )();
6691
6692
6693var getStyles, curCSS,
6694	rposition = /^(top|right|bottom|left)$/;
6695
6696if ( window.getComputedStyle ) {
6697	getStyles = function( elem ) {
6698
6699		// Support: IE<=11+, Firefox<=30+ (#15098, #14150)
6700		// IE throws on elements created in popups
6701		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
6702		var view = elem.ownerDocument.defaultView;
6703
6704		if ( !view || !view.opener ) {
6705			view = window;
6706		}
6707
6708		return view.getComputedStyle( elem );
6709	};
6710
6711	curCSS = function( elem, name, computed ) {
6712		var width, minWidth, maxWidth, ret,
6713			style = elem.style;
6714
6715		computed = computed || getStyles( elem );
6716
6717		// getPropertyValue is only needed for .css('filter') in IE9, see #12537
6718		ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;
6719
6720		// Support: Opera 12.1x only
6721		// Fall back to style even without computed
6722		// computed is undefined for elems on document fragments
6723		if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) {
6724			ret = jQuery.style( elem, name );
6725		}
6726
6727		if ( computed ) {
6728
6729			// A tribute to the "awesome hack by Dean Edwards"
6730			// Chrome < 17 and Safari 5.0 uses "computed value"
6731			// instead of "used value" for margin-right
6732			// Safari 5.1.7 (at least) returns percentage for a larger set of values,
6733			// but width seems to be reliably pixels
6734			// this is against the CSSOM draft spec:
6735			// http://dev.w3.org/csswg/cssom/#resolved-values
6736			if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {
6737
6738				// Remember the original values
6739				width = style.width;
6740				minWidth = style.minWidth;
6741				maxWidth = style.maxWidth;
6742
6743				// Put in the new values to get a computed value out
6744				style.minWidth = style.maxWidth = style.width = ret;
6745				ret = computed.width;
6746
6747				// Revert the changed values
6748				style.width = width;
6749				style.minWidth = minWidth;
6750				style.maxWidth = maxWidth;
6751			}
6752		}
6753
vendor: 4,152 bytes, lines 6754-6916
6754		// Support: IE
6755		// IE returns zIndex value as an integer.
6756		return ret === undefined ?
6757			ret :
6758			ret + "";
6759	};
6760} else if ( documentElement.currentStyle ) {
6761	getStyles = function( elem ) {
6762		return elem.currentStyle;
6763	};
6764
6765	curCSS = function( elem, name, computed ) {
6766		var left, rs, rsLeft, ret,
6767			style = elem.style;
6768
6769		computed = computed || getStyles( elem );
6770		ret = computed ? computed[ name ] : undefined;
6771
6772		// Avoid setting ret to empty string here
6773		// so we don't default to auto
6774		if ( ret == null && style && style[ name ] ) {
6775			ret = style[ name ];
6776		}
6777
6778		// From the awesome hack by Dean Edwards
6779		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
6780
6781		// If we're not dealing with a regular pixel number
6782		// but a number that has a weird ending, we need to convert it to pixels
6783		// but not position css attributes, as those are
6784		// proportional to the parent element instead
6785		// and we can't measure the parent instead because it
6786		// might trigger a "stacking dolls" problem
6787		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
6788
6789			// Remember the original values
6790			left = style.left;
6791			rs = elem.runtimeStyle;
6792			rsLeft = rs && rs.left;
6793
6794			// Put in the new values to get a computed value out
6795			if ( rsLeft ) {
6796				rs.left = elem.currentStyle.left;
6797			}
6798			style.left = name === "fontSize" ? "1em" : ret;
6799			ret = style.pixelLeft + "px";
6800
6801			// Revert the changed values
6802			style.left = left;
6803			if ( rsLeft ) {
6804				rs.left = rsLeft;
6805			}
6806		}
6807
6808		// Support: IE
6809		// IE returns zIndex value as an integer.
6810		return ret === undefined ?
6811			ret :
6812			ret + "" || "auto";
6813	};
6814}
6815
6816
6817
6818
6819function addGetHookIf( conditionFn, hookFn ) {
6820
6821	// Define the hook, we'll check on the first run if it's really needed.
6822	return {
6823		get: function() {
6824			if ( conditionFn() ) {
6825
6826				// Hook not needed (or it's not possible to use it due
6827				// to missing dependency), remove it.
6828				delete this.get;
6829				return;
6830			}
6831
6832			// Hook needed; redefine it so that the support test is not executed again.
6833			return ( this.get = hookFn ).apply( this, arguments );
6834		}
6835	};
6836}
6837
6838
6839var
6840
6841		ralpha = /alpha\([^)]*\)/i,
6842	ropacity = /opacity\s*=\s*([^)]*)/i,
6843
6844	// swappable if display is none or starts with table except
6845	// "table", "table-cell", or "table-caption"
6846	// see here for display values:
6847	// https://developer.mozilla.org/en-US/docs/CSS/display
6848	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
6849	rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
6850
6851	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
6852	cssNormalTransform = {
6853		letterSpacing: "0",
6854		fontWeight: "400"
6855	},
6856
6857	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
6858	emptyStyle = document.createElement( "div" ).style;
6859
6860
6861// return a css property mapped to a potentially vendor prefixed property
6862function vendorPropName( name ) {
6863
6864	// shortcut for names that are not vendor prefixed
6865	if ( name in emptyStyle ) {
6866		return name;
6867	}
6868
6869	// check for vendor prefixed names
6870	var capName = name.charAt( 0 ).toUpperCase() + name.slice( 1 ),
6871		i = cssPrefixes.length;
6872
6873	while ( i-- ) {
6874		name = cssPrefixes[ i ] + capName;
6875		if ( name in emptyStyle ) {
6876			return name;
6877		}
6878	}
6879}
6880
6881function showHide( elements, show ) {
6882	var display, elem, hidden,
6883		values = [],
6884		index = 0,
6885		length = elements.length;
6886
6887	for ( ; index < length; index++ ) {
6888		elem = elements[ index ];
6889		if ( !elem.style ) {
6890			continue;
6891		}
6892
6893		values[ index ] = jQuery._data( elem, "olddisplay" );
6894		display = elem.style.display;
6895		if ( show ) {
6896
6897			// Reset the inline display of this element to learn if it is
6898			// being hidden by cascaded rules or not
6899			if ( !values[ index ] && display === "none" ) {
6900				elem.style.display = "";
6901			}
6902
6903			// Set elements which have been overridden with display: none
6904			// in a stylesheet to whatever the default browser style is
6905			// for such an element
6906			if ( elem.style.display === "" && isHidden( elem ) ) {
6907				values[ index ] =
6908					jQuery._data( elem, "olddisplay", defaultDisplay( elem.nodeName ) );
6909			}
6910		} else {
6911			hidden = isHidden( elem );
6912
6913			if ( display && display !== "none" || !hidden ) {
6914				jQuery._data(
6915					elem,
6916					"olddisplay",
vendor: 6,043 bytes, lines 6917-7128
6917					hidden ? display : jQuery.css( elem, "display" )
6918				);
6919			}
6920		}
6921	}
6922
6923	// Set the display of most of the elements in a second loop
6924	// to avoid the constant reflow
6925	for ( index = 0; index < length; index++ ) {
6926		elem = elements[ index ];
6927		if ( !elem.style ) {
6928			continue;
6929		}
6930		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
6931			elem.style.display = show ? values[ index ] || "" : "none";
6932		}
6933	}
6934
6935	return elements;
6936}
6937
6938function setPositiveNumber( elem, value, subtract ) {
6939	var matches = rnumsplit.exec( value );
6940	return matches ?
6941
6942		// Guard against undefined "subtract", e.g., when used as in cssHooks
6943		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
6944		value;
6945}
6946
6947function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
6948	var i = extra === ( isBorderBox ? "border" : "content" ) ?
6949
6950		// If we already have the right measurement, avoid augmentation
6951		4 :
6952
6953		// Otherwise initialize for horizontal or vertical properties
6954		name === "width" ? 1 : 0,
6955
6956		val = 0;
6957
6958	for ( ; i < 4; i += 2 ) {
6959
6960		// both box models exclude margin, so add it if we want it
6961		if ( extra === "margin" ) {
6962			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
6963		}
6964
6965		if ( isBorderBox ) {
6966
6967			// border-box includes padding, so remove it if we want content
6968			if ( extra === "content" ) {
6969				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
6970			}
6971
6972			// at this point, extra isn't border nor margin, so remove border
6973			if ( extra !== "margin" ) {
6974				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
6975			}
6976		} else {
6977
6978			// at this point, extra isn't content, so add padding
6979			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
6980
6981			// at this point, extra isn't content nor padding, so add border
6982			if ( extra !== "padding" ) {
6983				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
6984			}
6985		}
6986	}
6987
6988	return val;
6989}
6990
6991function getWidthOrHeight( elem, name, extra ) {
6992
6993	// Start with offset property, which is equivalent to the border-box value
6994	var valueIsBorderBox = true,
6995		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
6996		styles = getStyles( elem ),
6997		isBorderBox = support.boxSizing &&
6998			jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
6999
7000	// some non-html elements return undefined for offsetWidth, so check for null/undefined
7001	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
7002	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
7003	if ( val <= 0 || val == null ) {
7004
7005		// Fall back to computed then uncomputed css if necessary
7006		val = curCSS( elem, name, styles );
7007		if ( val < 0 || val == null ) {
7008			val = elem.style[ name ];
7009		}
7010
7011		// Computed unit is not pixels. Stop here and return.
7012		if ( rnumnonpx.test( val ) ) {
7013			return val;
7014		}
7015
7016		// we need the check for style in case a browser which returns unreliable values
7017		// for getComputedStyle silently falls back to the reliable elem.style
7018		valueIsBorderBox = isBorderBox &&
7019			( support.boxSizingReliable() || val === elem.style[ name ] );
7020
7021		// Normalize "", auto, and prepare for extra
7022		val = parseFloat( val ) || 0;
7023	}
7024
7025	// use the active box-sizing model to add/subtract irrelevant styles
7026	return ( val +
7027		augmentWidthOrHeight(
7028			elem,
7029			name,
7030			extra || ( isBorderBox ? "border" : "content" ),
7031			valueIsBorderBox,
7032			styles
7033		)
7034	) + "px";
7035}
7036
7037jQuery.extend( {
7038
7039	// Add in style property hooks for overriding the default
7040	// behavior of getting and setting a style property
7041	cssHooks: {
7042		opacity: {
7043			get: function( elem, computed ) {
7044				if ( computed ) {
7045
7046					// We should always get a number back from opacity
7047					var ret = curCSS( elem, "opacity" );
7048					return ret === "" ? "1" : ret;
7049				}
7050			}
7051		}
7052	},
7053
7054	// Don't automatically add "px" to these possibly-unitless properties
7055	cssNumber: {
7056		"animationIterationCount": true,
7057		"columnCount": true,
7058		"fillOpacity": true,
7059		"flexGrow": true,
7060		"flexShrink": true,
7061		"fontWeight": true,
7062		"lineHeight": true,
7063		"opacity": true,
7064		"order": true,
7065		"orphans": true,
7066		"widows": true,
7067		"zIndex": true,
7068		"zoom": true
7069	},
7070
7071	// Add in properties whose names you wish to fix before
7072	// setting or getting the value
7073	cssProps: {
7074
7075		// normalize float css property
7076		"float": support.cssFloat ? "cssFloat" : "styleFloat"
7077	},
7078
7079	// Get and set the style property on a DOM Node
7080	style: function( elem, name, value, extra ) {
7081
7082		// Don't set styles on text and comment nodes
7083		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
7084			return;
7085		}
7086
7087		// Make sure that we're working with the right name
7088		var ret, type, hooks,
7089			origName = jQuery.camelCase( name ),
7090			style = elem.style;
7091
7092		name = jQuery.cssProps[ origName ] ||
7093			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
7094
7095		// gets hook for the prefixed version
7096		// followed by the unprefixed version
7097		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
7098
7099		// Check if we're setting a value
7100		if ( value !== undefined ) {
7101			type = typeof value;
7102
7103			// Convert "+=" or "-=" to relative numbers (#7345)
7104			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
7105				value = adjustCSS( elem, name, ret );
7106
7107				// Fixes bug #9237
7108				type = "number";
7109			}
7110
7111			// Make sure that null and NaN values aren't set. See: #7116
7112			if ( value == null || value !== value ) {
7113				return;
7114			}
7115
7116			// If a number was passed in, add the unit (except for certain CSS properties)
7117			if ( type === "number" ) {
7118				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
7119			}
7120
7121			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
7122			// but it would mean to define eight
7123			// (for every problematic property) identical functions
7124			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
7125				style[ name ] = "inherit";
7126			}
7127
7128			// If a hook was provided, use that value, otherwise just set the specified value
vendor: 4,586 bytes, lines 7129-7284
7129			if ( !hooks || !( "set" in hooks ) ||
7130				( value = hooks.set( elem, value, extra ) ) !== undefined ) {
7131
7132				// Support: IE
7133				// Swallow errors from 'invalid' CSS values (#5509)
7134				try {
7135					style[ name ] = value;
7136				} catch ( e ) {}
7137			}
7138
7139		} else {
7140
7141			// If a hook was provided get the non-computed value from there
7142			if ( hooks && "get" in hooks &&
7143				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
7144
7145				return ret;
7146			}
7147
7148			// Otherwise just get the value from the style object
7149			return style[ name ];
7150		}
7151	},
7152
7153	css: function( elem, name, extra, styles ) {
7154		var num, val, hooks,
7155			origName = jQuery.camelCase( name );
7156
7157		// Make sure that we're working with the right name
7158		name = jQuery.cssProps[ origName ] ||
7159			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
7160
7161		// gets hook for the prefixed version
7162		// followed by the unprefixed version
7163		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
7164
7165		// If a hook was provided get the computed value from there
7166		if ( hooks && "get" in hooks ) {
7167			val = hooks.get( elem, true, extra );
7168		}
7169
7170		// Otherwise, if a way to get the computed value exists, use that
7171		if ( val === undefined ) {
7172			val = curCSS( elem, name, styles );
7173		}
7174
7175		//convert "normal" to computed value
7176		if ( val === "normal" && name in cssNormalTransform ) {
7177			val = cssNormalTransform[ name ];
7178		}
7179
7180		// Return, converting to number if forced or a qualifier was provided and val looks numeric
7181		if ( extra === "" || extra ) {
7182			num = parseFloat( val );
7183			return extra === true || isFinite( num ) ? num || 0 : val;
7184		}
7185		return val;
7186	}
7187} );
7188
7189jQuery.each( [ "height", "width" ], function( i, name ) {
7190	jQuery.cssHooks[ name ] = {
7191		get: function( elem, computed, extra ) {
7192			if ( computed ) {
7193
7194				// certain elements can have dimension info if we invisibly show them
7195				// however, it must have a current display style that would benefit from this
7196				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
7197					elem.offsetWidth === 0 ?
7198						swap( elem, cssShow, function() {
7199							return getWidthOrHeight( elem, name, extra );
7200						} ) :
7201						getWidthOrHeight( elem, name, extra );
7202			}
7203		},
7204
7205		set: function( elem, value, extra ) {
7206			var styles = extra && getStyles( elem );
7207			return setPositiveNumber( elem, value, extra ?
7208				augmentWidthOrHeight(
7209					elem,
7210					name,
7211					extra,
7212					support.boxSizing &&
7213						jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
7214					styles
7215				) : 0
7216			);
7217		}
7218	};
7219} );
7220
7221if ( !support.opacity ) {
7222	jQuery.cssHooks.opacity = {
7223		get: function( elem, computed ) {
7224
7225			// IE uses filters for opacity
7226			return ropacity.test( ( computed && elem.currentStyle ?
7227				elem.currentStyle.filter :
7228				elem.style.filter ) || "" ) ?
7229					( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
7230					computed ? "1" : "";
7231		},
7232
7233		set: function( elem, value ) {
7234			var style = elem.style,
7235				currentStyle = elem.currentStyle,
7236				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
7237				filter = currentStyle && currentStyle.filter || style.filter || "";
7238
7239			// IE has trouble with opacity if it does not have layout
7240			// Force it by setting the zoom level
7241			style.zoom = 1;
7242
7243			// if setting opacity to 1, and no other filters exist -
7244			// attempt to remove filter attribute #6652
7245			// if value === "", then remove inline opacity #12685
7246			if ( ( value >= 1 || value === "" ) &&
7247					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
7248					style.removeAttribute ) {
7249
7250				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
7251				// if "filter:" is present at all, clearType is disabled, we want to avoid this
7252				// style.removeAttribute is IE Only, but so apparently is this code path...
7253				style.removeAttribute( "filter" );
7254
7255				// if there is no filter style applied in a css rule
7256				// or unset inline opacity, we are done
7257				if ( value === "" || currentStyle && !currentStyle.filter ) {
7258					return;
7259				}
7260			}
7261
7262			// otherwise, set new filter values
7263			style.filter = ralpha.test( filter ) ?
7264				filter.replace( ralpha, opacity ) :
7265				filter + " " + opacity;
7266		}
7267	};
7268}
7269
7270jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
7271	function( elem, computed ) {
7272		if ( computed ) {
7273			return swap( elem, { "display": "inline-block" },
7274				curCSS, [ elem, "marginRight" ] );
7275		}
7276	}
7277);
7278
7279jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
7280	function( elem, computed ) {
7281		if ( computed ) {
7282			return (
7283				parseFloat( curCSS( elem, "marginLeft" ) ) ||
7284
vendor: 4,625 bytes, lines 7285-7463
7285				// Support: IE<=11+
7286				// Running getBoundingClientRect on a disconnected node in IE throws an error
7287				// Support: IE8 only
7288				// getClientRects() errors on disconnected elems
7289				( jQuery.contains( elem.ownerDocument, elem ) ?
7290					elem.getBoundingClientRect().left -
7291						swap( elem, { marginLeft: 0 }, function() {
7292							return elem.getBoundingClientRect().left;
7293						} ) :
7294					0
7295				)
7296			) + "px";
7297		}
7298	}
7299);
7300
7301// These hooks are used by animate to expand properties
7302jQuery.each( {
7303	margin: "",
7304	padding: "",
7305	border: "Width"
7306}, function( prefix, suffix ) {
7307	jQuery.cssHooks[ prefix + suffix ] = {
7308		expand: function( value ) {
7309			var i = 0,
7310				expanded = {},
7311
7312				// assumes a single number if not a string
7313				parts = typeof value === "string" ? value.split( " " ) : [ value ];
7314
7315			for ( ; i < 4; i++ ) {
7316				expanded[ prefix + cssExpand[ i ] + suffix ] =
7317					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
7318			}
7319
7320			return expanded;
7321		}
7322	};
7323
7324	if ( !rmargin.test( prefix ) ) {
7325		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
7326	}
7327} );
7328
7329jQuery.fn.extend( {
7330	css: function( name, value ) {
7331		return access( this, function( elem, name, value ) {
7332			var styles, len,
7333				map = {},
7334				i = 0;
7335
7336			if ( jQuery.isArray( name ) ) {
7337				styles = getStyles( elem );
7338				len = name.length;
7339
7340				for ( ; i < len; i++ ) {
7341					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
7342				}
7343
7344				return map;
7345			}
7346
7347			return value !== undefined ?
7348				jQuery.style( elem, name, value ) :
7349				jQuery.css( elem, name );
7350		}, name, value, arguments.length > 1 );
7351	},
7352	show: function() {
7353		return showHide( this, true );
7354	},
7355	hide: function() {
7356		return showHide( this );
7357	},
7358	toggle: function( state ) {
7359		if ( typeof state === "boolean" ) {
7360			return state ? this.show() : this.hide();
7361		}
7362
7363		return this.each( function() {
7364			if ( isHidden( this ) ) {
7365				jQuery( this ).show();
7366			} else {
7367				jQuery( this ).hide();
7368			}
7369		} );
7370	}
7371} );
7372
7373
7374function Tween( elem, options, prop, end, easing ) {
7375	return new Tween.prototype.init( elem, options, prop, end, easing );
7376}
7377jQuery.Tween = Tween;
7378
7379Tween.prototype = {
7380	constructor: Tween,
7381	init: function( elem, options, prop, end, easing, unit ) {
7382		this.elem = elem;
7383		this.prop = prop;
7384		this.easing = easing || jQuery.easing._default;
7385		this.options = options;
7386		this.start = this.now = this.cur();
7387		this.end = end;
7388		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
7389	},
7390	cur: function() {
7391		var hooks = Tween.propHooks[ this.prop ];
7392
7393		return hooks && hooks.get ?
7394			hooks.get( this ) :
7395			Tween.propHooks._default.get( this );
7396	},
7397	run: function( percent ) {
7398		var eased,
7399			hooks = Tween.propHooks[ this.prop ];
7400
7401		if ( this.options.duration ) {
7402			this.pos = eased = jQuery.easing[ this.easing ](
7403				percent, this.options.duration * percent, 0, 1, this.options.duration
7404			);
7405		} else {
7406			this.pos = eased = percent;
7407		}
7408		this.now = ( this.end - this.start ) * eased + this.start;
7409
7410		if ( this.options.step ) {
7411			this.options.step.call( this.elem, this.now, this );
7412		}
7413
7414		if ( hooks && hooks.set ) {
7415			hooks.set( this );
7416		} else {
7417			Tween.propHooks._default.set( this );
7418		}
7419		return this;
7420	}
7421};
7422
7423Tween.prototype.init.prototype = Tween.prototype;
7424
7425Tween.propHooks = {
7426	_default: {
7427		get: function( tween ) {
7428			var result;
7429
7430			// Use a property on the element directly when it is not a DOM element,
7431			// or when there is no matching style property that exists.
7432			if ( tween.elem.nodeType !== 1 ||
7433				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
7434				return tween.elem[ tween.prop ];
7435			}
7436
7437			// passing an empty string as a 3rd parameter to .css will automatically
7438			// attempt a parseFloat and fallback to a string if the parse fails
7439			// so, simple values such as "10px" are parsed to Float.
7440			// complex values such as "rotate(1rad)" are returned as is.
7441			result = jQuery.css( tween.elem, tween.prop, "" );
7442
7443			// Empty strings, null, undefined and "auto" are converted to 0.
7444			return !result || result === "auto" ? 0 : result;
7445		},
7446		set: function( tween ) {
7447
7448			// use step hook for back compat - use cssHook if its there - use .style if its
7449			// available and use plain properties where available
7450			if ( jQuery.fx.step[ tween.prop ] ) {
7451				jQuery.fx.step[ tween.prop ]( tween );
7452			} else if ( tween.elem.nodeType === 1 &&
7453				( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
7454					jQuery.cssHooks[ tween.prop ] ) ) {
7455				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
7456			} else {
7457				tween.elem[ tween.prop ] = tween.now;
7458			}
7459		}
7460	}
7461};
7462
7463// Support: IE <=9
vendor: 5,654 bytes, lines 7464-7680
7464// Panic based approach to setting things on disconnected nodes
7465
7466Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
7467	set: function( tween ) {
7468		if ( tween.elem.nodeType && tween.elem.parentNode ) {
7469			tween.elem[ tween.prop ] = tween.now;
7470		}
7471	}
7472};
7473
7474jQuery.easing = {
7475	linear: function( p ) {
7476		return p;
7477	},
7478	swing: function( p ) {
7479		return 0.5 - Math.cos( p * Math.PI ) / 2;
7480	},
7481	_default: "swing"
7482};
7483
7484jQuery.fx = Tween.prototype.init;
7485
7486// Back Compat <1.8 extension point
7487jQuery.fx.step = {};
7488
7489
7490
7491
7492var
7493	fxNow, timerId,
7494	rfxtypes = /^(?:toggle|show|hide)$/,
7495	rrun = /queueHooks$/;
7496
7497// Animations created synchronously will run synchronously
7498function createFxNow() {
7499	window.setTimeout( function() {
7500		fxNow = undefined;
7501	} );
7502	return ( fxNow = jQuery.now() );
7503}
7504
7505// Generate parameters to create a standard animation
7506function genFx( type, includeWidth ) {
7507	var which,
7508		attrs = { height: type },
7509		i = 0;
7510
7511	// if we include width, step value is 1 to do all cssExpand values,
7512	// if we don't include width, step value is 2 to skip over Left and Right
7513	includeWidth = includeWidth ? 1 : 0;
7514	for ( ; i < 4 ; i += 2 - includeWidth ) {
7515		which = cssExpand[ i ];
7516		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
7517	}
7518
7519	if ( includeWidth ) {
7520		attrs.opacity = attrs.width = type;
7521	}
7522
7523	return attrs;
7524}
7525
7526function createTween( value, prop, animation ) {
7527	var tween,
7528		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
7529		index = 0,
7530		length = collection.length;
7531	for ( ; index < length; index++ ) {
7532		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
7533
7534			// we're done with this property
7535			return tween;
7536		}
7537	}
7538}
7539
7540function defaultPrefilter( elem, props, opts ) {
7541	/* jshint validthis: true */
7542	var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
7543		anim = this,
7544		orig = {},
7545		style = elem.style,
7546		hidden = elem.nodeType && isHidden( elem ),
7547		dataShow = jQuery._data( elem, "fxshow" );
7548
7549	// handle queue: false promises
7550	if ( !opts.queue ) {
7551		hooks = jQuery._queueHooks( elem, "fx" );
7552		if ( hooks.unqueued == null ) {
7553			hooks.unqueued = 0;
7554			oldfire = hooks.empty.fire;
7555			hooks.empty.fire = function() {
7556				if ( !hooks.unqueued ) {
7557					oldfire();
7558				}
7559			};
7560		}
7561		hooks.unqueued++;
7562
7563		anim.always( function() {
7564
7565			// doing this makes sure that the complete handler will be called
7566			// before this completes
7567			anim.always( function() {
7568				hooks.unqueued--;
7569				if ( !jQuery.queue( elem, "fx" ).length ) {
7570					hooks.empty.fire();
7571				}
7572			} );
7573		} );
7574	}
7575
7576	// height/width overflow pass
7577	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
7578
7579		// Make sure that nothing sneaks out
7580		// Record all 3 overflow attributes because IE does not
7581		// change the overflow attribute when overflowX and
7582		// overflowY are set to the same value
7583		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
7584
7585		// Set display property to inline-block for height/width
7586		// animations on inline elements that are having width/height animated
7587		display = jQuery.css( elem, "display" );
7588
7589		// Test default display if display is currently "none"
7590		checkDisplay = display === "none" ?
7591			jQuery._data( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
7592
7593		if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
7594
7595			// inline-level elements accept inline-block;
7596			// block-level elements need to be inline with layout
7597			if ( !support.inlineBlockNeedsLayout || defaultDisplay( elem.nodeName ) === "inline" ) {
7598				style.display = "inline-block";
7599			} else {
7600				style.zoom = 1;
7601			}
7602		}
7603	}
7604
7605	if ( opts.overflow ) {
7606		style.overflow = "hidden";
7607		if ( !support.shrinkWrapBlocks() ) {
7608			anim.always( function() {
7609				style.overflow = opts.overflow[ 0 ];
7610				style.overflowX = opts.overflow[ 1 ];
7611				style.overflowY = opts.overflow[ 2 ];
7612			} );
7613		}
7614	}
7615
7616	// show/hide pass
7617	for ( prop in props ) {
7618		value = props[ prop ];
7619		if ( rfxtypes.exec( value ) ) {
7620			delete props[ prop ];
7621			toggle = toggle || value === "toggle";
7622			if ( value === ( hidden ? "hide" : "show" ) ) {
7623
7624				// If there is dataShow left over from a stopped hide or show
7625				// and we are going to proceed with show, we should pretend to be hidden
7626				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
7627					hidden = true;
7628				} else {
7629					continue;
7630				}
7631			}
7632			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
7633
7634		// Any non-fx value stops us from restoring the original display value
7635		} else {
7636			display = undefined;
7637		}
7638	}
7639
7640	if ( !jQuery.isEmptyObject( orig ) ) {
7641		if ( dataShow ) {
7642			if ( "hidden" in dataShow ) {
7643				hidden = dataShow.hidden;
7644			}
7645		} else {
7646			dataShow = jQuery._data( elem, "fxshow", {} );
7647		}
7648
7649		// store state if its toggle - enables .stop().toggle() to "reverse"
7650		if ( toggle ) {
7651			dataShow.hidden = !hidden;
7652		}
7653		if ( hidden ) {
7654			jQuery( elem ).show();
7655		} else {
7656			anim.done( function() {
7657				jQuery( elem ).hide();
7658			} );
7659		}
7660		anim.done( function() {
7661			var prop;
7662			jQuery._removeData( elem, "fxshow" );
7663			for ( prop in orig ) {
7664				jQuery.style( elem, prop, orig[ prop ] );
7665			}
7666		} );
7667		for ( prop in orig ) {
7668			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
7669
7670			if ( !( prop in dataShow ) ) {
7671				dataShow[ prop ] = tween.start;
7672				if ( hidden ) {
7673					tween.end = tween.start;
7674					tween.start = prop === "width" || prop === "height" ? 1 : 0;
7675				}
7676			}
7677		}
7678
7679	// If this is a noop like .hide().hide(), restore an overwritten display value
7680	} else if ( ( display === "none" ? defaultDisplay( elem.nodeName ) : dis
vendor: 10,851 bytes, lines 7680-8112
7680play ) === "inline" ) {
7681		style.display = display;
7682	}
7683}
7684
7685function propFilter( props, specialEasing ) {
7686	var index, name, easing, value, hooks;
7687
7688	// camelCase, specialEasing and expand cssHook pass
7689	for ( index in props ) {
7690		name = jQuery.camelCase( index );
7691		easing = specialEasing[ name ];
7692		value = props[ index ];
7693		if ( jQuery.isArray( value ) ) {
7694			easing = value[ 1 ];
7695			value = props[ index ] = value[ 0 ];
7696		}
7697
7698		if ( index !== name ) {
7699			props[ name ] = value;
7700			delete props[ index ];
7701		}
7702
7703		hooks = jQuery.cssHooks[ name ];
7704		if ( hooks && "expand" in hooks ) {
7705			value = hooks.expand( value );
7706			delete props[ name ];
7707
7708			// not quite $.extend, this wont overwrite keys already present.
7709			// also - reusing 'index' from above because we have the correct "name"
7710			for ( index in value ) {
7711				if ( !( index in props ) ) {
7712					props[ index ] = value[ index ];
7713					specialEasing[ index ] = easing;
7714				}
7715			}
7716		} else {
7717			specialEasing[ name ] = easing;
7718		}
7719	}
7720}
7721
7722function Animation( elem, properties, options ) {
7723	var result,
7724		stopped,
7725		index = 0,
7726		length = Animation.prefilters.length,
7727		deferred = jQuery.Deferred().always( function() {
7728
7729			// don't match elem in the :animated selector
7730			delete tick.elem;
7731		} ),
7732		tick = function() {
7733			if ( stopped ) {
7734				return false;
7735			}
7736			var currentTime = fxNow || createFxNow(),
7737				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
7738
7739				// Support: Android 2.3
7740				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
7741				temp = remaining / animation.duration || 0,
7742				percent = 1 - temp,
7743				index = 0,
7744				length = animation.tweens.length;
7745
7746			for ( ; index < length ; index++ ) {
7747				animation.tweens[ index ].run( percent );
7748			}
7749
7750			deferred.notifyWith( elem, [ animation, percent, remaining ] );
7751
7752			if ( percent < 1 && length ) {
7753				return remaining;
7754			} else {
7755				deferred.resolveWith( elem, [ animation ] );
7756				return false;
7757			}
7758		},
7759		animation = deferred.promise( {
7760			elem: elem,
7761			props: jQuery.extend( {}, properties ),
7762			opts: jQuery.extend( true, {
7763				specialEasing: {},
7764				easing: jQuery.easing._default
7765			}, options ),
7766			originalProperties: properties,
7767			originalOptions: options,
7768			startTime: fxNow || createFxNow(),
7769			duration: options.duration,
7770			tweens: [],
7771			createTween: function( prop, end ) {
7772				var tween = jQuery.Tween( elem, animation.opts, prop, end,
7773						animation.opts.specialEasing[ prop ] || animation.opts.easing );
7774				animation.tweens.push( tween );
7775				return tween;
7776			},
7777			stop: function( gotoEnd ) {
7778				var index = 0,
7779
7780					// if we are going to the end, we want to run all the tweens
7781					// otherwise we skip this part
7782					length = gotoEnd ? animation.tweens.length : 0;
7783				if ( stopped ) {
7784					return this;
7785				}
7786				stopped = true;
7787				for ( ; index < length ; index++ ) {
7788					animation.tweens[ index ].run( 1 );
7789				}
7790
7791				// resolve when we played the last frame
7792				// otherwise, reject
7793				if ( gotoEnd ) {
7794					deferred.notifyWith( elem, [ animation, 1, 0 ] );
7795					deferred.resolveWith( elem, [ animation, gotoEnd ] );
7796				} else {
7797					deferred.rejectWith( elem, [ animation, gotoEnd ] );
7798				}
7799				return this;
7800			}
7801		} ),
7802		props = animation.props;
7803
7804	propFilter( props, animation.opts.specialEasing );
7805
7806	for ( ; index < length ; index++ ) {
7807		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
7808		if ( result ) {
7809			if ( jQuery.isFunction( result.stop ) ) {
7810				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
7811					jQuery.proxy( result.stop, result );
7812			}
7813			return result;
7814		}
7815	}
7816
7817	jQuery.map( props, createTween, animation );
7818
7819	if ( jQuery.isFunction( animation.opts.start ) ) {
7820		animation.opts.start.call( elem, animation );
7821	}
7822
7823	jQuery.fx.timer(
7824		jQuery.extend( tick, {
7825			elem: elem,
7826			anim: animation,
7827			queue: animation.opts.queue
7828		} )
7829	);
7830
7831	// attach callbacks from options
7832	return animation.progress( animation.opts.progress )
7833		.done( animation.opts.done, animation.opts.complete )
7834		.fail( animation.opts.fail )
7835		.always( animation.opts.always );
7836}
7837
7838jQuery.Animation = jQuery.extend( Animation, {
7839
7840	tweeners: {
7841		"*": [ function( prop, value ) {
7842			var tween = this.createTween( prop, value );
7843			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
7844			return tween;
7845		} ]
7846	},
7847
7848	tweener: function( props, callback ) {
7849		if ( jQuery.isFunction( props ) ) {
7850			callback = props;
7851			props = [ "*" ];
7852		} else {
7853			props = props.match( rnotwhite );
7854		}
7855
7856		var prop,
7857			index = 0,
7858			length = props.length;
7859
7860		for ( ; index < length ; index++ ) {
7861			prop = props[ index ];
7862			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
7863			Animation.tweeners[ prop ].unshift( callback );
7864		}
7865	},
7866
7867	prefilters: [ defaultPrefilter ],
7868
7869	prefilter: function( callback, prepend ) {
7870		if ( prepend ) {
7871			Animation.prefilters.unshift( callback );
7872		} else {
7873			Animation.prefilters.push( callback );
7874		}
7875	}
7876} );
7877
7878jQuery.speed = function( speed, easing, fn ) {
7879	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
7880		complete: fn || !fn && easing ||
7881			jQuery.isFunction( speed ) && speed,
7882		duration: speed,
7883		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
7884	};
7885
7886	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
7887		opt.duration in jQuery.fx.speeds ?
7888			jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
7889
7890	// normalize opt.queue - true/undefined/null -> "fx"
7891	if ( opt.queue == null || opt.queue === true ) {
7892		opt.queue = "fx";
7893	}
7894
7895	// Queueing
7896	opt.old = opt.complete;
7897
7898	opt.complete = function() {
7899		if ( jQuery.isFunction( opt.old ) ) {
7900			opt.old.call( this );
7901		}
7902
7903		if ( opt.queue ) {
7904			jQuery.dequeue( this, opt.queue );
7905		}
7906	};
7907
7908	return opt;
7909};
7910
7911jQuery.fn.extend( {
7912	fadeTo: function( speed, to, easing, callback ) {
7913
7914		// show any hidden elements after setting opacity to 0
7915		return this.filter( isHidden ).css( "opacity", 0 ).show()
7916
7917			// animate to the value specified
7918			.end().animate( { opacity: to }, speed, easing, callback );
7919	},
7920	animate: function( prop, speed, easing, callback ) {
7921		var empty = jQuery.isEmptyObject( prop ),
7922			optall = jQuery.speed( speed, easing, callback ),
7923			doAnimation = function() {
7924
7925				// Operate on a copy of prop so per-property easing won't be lost
7926				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
7927
7928				// Empty animations, or finishing resolves immediately
7929				if ( empty || jQuery._data( this, "finish" ) ) {
7930					anim.stop( true );
7931				}
7932			};
7933			doAnimation.finish = doAnimation;
7934
7935		return empty || optall.queue === false ?
7936			this.each( doAnimation ) :
7937			this.queue( optall.queue, doAnimation );
7938	},
7939	stop: function( type, clearQueue, gotoEnd ) {
7940		var stopQueue = function( hooks ) {
7941			var stop = hooks.stop;
7942			delete hooks.stop;
7943			stop( gotoEnd );
7944		};
7945
7946		if ( typeof type !== "string" ) {
7947			gotoEnd = clearQueue;
7948			clearQueue = type;
7949			type = undefined;
7950		}
7951		if ( clearQueue && type !== false ) {
7952			this.queue( type || "fx", [] );
7953		}
7954
7955		return this.each( function() {
7956			var dequeue = true,
7957				index = type != null && type + "queueHooks",
7958				timers = jQuery.timers,
7959				data = jQuery._data( this );
7960
7961			if ( index ) {
7962				if ( data[ index ] && data[ index ].stop ) {
7963					stopQueue( data[ index ] );
7964				}
7965			} else {
7966				for ( index in data ) {
7967					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
7968						stopQueue( data[ index ] );
7969					}
7970				}
7971			}
7972
7973			for ( index = timers.length; index--; ) {
7974				if ( timers[ index ].elem === this &&
7975					( type == null || timers[ index ].queue === type ) ) {
7976
7977					timers[ index ].anim.stop( gotoEnd );
7978					dequeue = false;
7979					timers.splice( index, 1 );
7980				}
7981			}
7982
7983			// start the next in the queue if the last step wasn't forced
7984			// timers currently will call their complete callbacks, which will dequeue
7985			// but only if they were gotoEnd
7986			if ( dequeue || !gotoEnd ) {
7987				jQuery.dequeue( this, type );
7988			}
7989		} );
7990	},
7991	finish: function( type ) {
7992		if ( type !== false ) {
7993			type = type || "fx";
7994		}
7995		return this.each( function() {
7996			var index,
7997				data = jQuery._data( this ),
7998				queue = data[ type + "queue" ],
7999				hooks = data[ type + "queueHooks" ],
8000				timers = jQuery.timers,
8001				length = queue ? queue.length : 0;
8002
8003			// enable finishing flag on private data
8004			data.finish = true;
8005
8006			// empty the queue first
8007			jQuery.queue( this, type, [] );
8008
8009			if ( hooks && hooks.stop ) {
8010				hooks.stop.call( this, true );
8011			}
8012
8013			// look for any active animations, and finish them
8014			for ( index = timers.length; index--; ) {
8015				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
8016					timers[ index ].anim.stop( true );
8017					timers.splice( index, 1 );
8018				}
8019			}
8020
8021			// look for any animations in the old queue and finish them
8022			for ( index = 0; index < length; index++ ) {
8023				if ( queue[ index ] && queue[ index ].finish ) {
8024					queue[ index ].finish.call( this );
8025				}
8026			}
8027
8028			// turn off finishing flag
8029			delete data.finish;
8030		} );
8031	}
8032} );
8033
8034jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
8035	var cssFn = jQuery.fn[ name ];
8036	jQuery.fn[ name ] = function( speed, easing, callback ) {
8037		return speed == null || typeof speed === "boolean" ?
8038			cssFn.apply( this, arguments ) :
8039			this.animate( genFx( name, true ), speed, easing, callback );
8040	};
8041} );
8042
8043// Generate shortcuts for custom animations
8044jQuery.each( {
8045	slideDown: genFx( "show" ),
8046	slideUp: genFx( "hide" ),
8047	slideToggle: genFx( "toggle" ),
8048	fadeIn: { opacity: "show" },
8049	fadeOut: { opacity: "hide" },
8050	fadeToggle: { opacity: "toggle" }
8051}, function( name, props ) {
8052	jQuery.fn[ name ] = function( speed, easing, callback ) {
8053		return this.animate( props, speed, easing, callback );
8054	};
8055} );
8056
8057jQuery.timers = [];
8058jQuery.fx.tick = function() {
8059	var timer,
8060		timers = jQuery.timers,
8061		i = 0;
8062
8063	fxNow = jQuery.now();
8064
8065	for ( ; i < timers.length; i++ ) {
8066		timer = timers[ i ];
8067
8068		// Checks the timer has not already been removed
8069		if ( !timer() && timers[ i ] === timer ) {
8070			timers.splice( i--, 1 );
8071		}
8072	}
8073
8074	if ( !timers.length ) {
8075		jQuery.fx.stop();
8076	}
8077	fxNow = undefined;
8078};
8079
8080jQuery.fx.timer = function( timer ) {
8081	jQuery.timers.push( timer );
8082	if ( timer() ) {
8083		jQuery.fx.start();
8084	} else {
8085		jQuery.timers.pop();
8086	}
8087};
8088
8089jQuery.fx.interval = 13;
8090
8091jQuery.fx.start = function() {
8092	if ( !timerId ) {
8093		timerId = window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
8094	}
8095};
8096
8097jQuery.fx.stop = function() {
8098	window.clearInterval( timerId );
8099	timerId = null;
8100};
8101
8102jQuery.fx.speeds = {
8103	slow: 600,
8104	fast: 200,
8105
8106	// Default speed
8107	_default: 400
8108};
8109
8110
8111// Based off of the plugin by Clint Helfers, with permission.
8112// http://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
8113jQuery.fn.delay = function( time, type ) {
8114	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
8115	type = type || "fx";
8116
8117	return this.queue( type, function( next, hooks ) {
8118		var timeout = window.setTimeout( next, time );
8119		hooks.stop = function() {
8120			window.clearTimeout( timeout );
8121		};
8122	} );
8123};
8124
8125
8126( function() {
8127	var a,
8128		input = document.createElement( "input" ),
8129		div = document.createElement( "div" ),
8130		select = document.createElement( "select" ),
8131		opt = select.appendChild( document.createElement( "option" ) );
8132
8133	// Setup
8134	div = document.createElement( "div" );
8135	div.setAttribute( "className", "t" );
8136	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
8137	a = div.getElementsByTagName( "a" )[ 0 ];
8138
8139	// Support: Windows Web Apps (WWA)
8140	// `type` must use .setAttribute for WWA (#14901)
8141	input.setAttribute( "type", "checkbox" );
8142	div.appendChild( input );
8143
8144	a = div.getElementsByTagName( "a" )[ 0 ];
8145
8146	// First batch of tests.
8147	a.style.cssText = "top:1px";
8148
8149	// Test setAttribute on camelCase class.
8150	// If it works, we need attrFixes when doing get/setAttribute (ie6/7)
vendor: 2,696 bytes, lines 8151-8252
8151	support.getSetAttribute = div.className !== "t";
8152
8153	// Get the style information from getAttribute
8154	// (IE uses .cssText instead)
8155	support.style = /top/.test( a.getAttribute( "style" ) );
8156
8157	// Make sure that URLs aren't manipulated
8158	// (IE normalizes it by default)
8159	support.hrefNormalized = a.getAttribute( "href" ) === "/a";
8160
8161	// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
8162	support.checkOn = !!input.value;
8163
8164	// Make sure that a selected-by-default option has a working selected property.
8165	// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
8166	support.optSelected = opt.selected;
8167
8168	// Tests for enctype support on a form (#6743)
8169	support.enctype = !!document.createElement( "form" ).enctype;
8170
8171	// Make sure that the options inside disabled selects aren't marked as disabled
8172	// (WebKit marks them as disabled)
8173	select.disabled = true;
8174	support.optDisabled = !opt.disabled;
8175
8176	// Support: IE8 only
8177	// Check if we can trust getAttribute("value")
8178	input = document.createElement( "input" );
8179	input.setAttribute( "value", "" );
8180	support.input = input.getAttribute( "value" ) === "";
8181
8182	// Check if an input maintains its value after becoming a radio
8183	input.value = "t";
8184	input.setAttribute( "type", "radio" );
8185	support.radioValue = input.value === "t";
8186} )();
8187
8188
8189var rreturn = /\r/g,
8190	rspaces = /[\x20\t\r\n\f]+/g;
8191
8192jQuery.fn.extend( {
8193	val: function( value ) {
8194		var hooks, ret, isFunction,
8195			elem = this[ 0 ];
8196
8197		if ( !arguments.length ) {
8198			if ( elem ) {
8199				hooks = jQuery.valHooks[ elem.type ] ||
8200					jQuery.valHooks[ elem.nodeName.toLowerCase() ];
8201
8202				if (
8203					hooks &&
8204					"get" in hooks &&
8205					( ret = hooks.get( elem, "value" ) ) !== undefined
8206				) {
8207					return ret;
8208				}
8209
8210				ret = elem.value;
8211
8212				return typeof ret === "string" ?
8213
8214					// handle most common string cases
8215					ret.replace( rreturn, "" ) :
8216
8217					// handle cases where value is null/undef or number
8218					ret == null ? "" : ret;
8219			}
8220
8221			return;
8222		}
8223
8224		isFunction = jQuery.isFunction( value );
8225
8226		return this.each( function( i ) {
8227			var val;
8228
8229			if ( this.nodeType !== 1 ) {
8230				return;
8231			}
8232
8233			if ( isFunction ) {
8234				val = value.call( this, i, jQuery( this ).val() );
8235			} else {
8236				val = value;
8237			}
8238
8239			// Treat null/undefined as ""; convert numbers to string
8240			if ( val == null ) {
8241				val = "";
8242			} else if ( typeof val === "number" ) {
8243				val += "";
8244			} else if ( jQuery.isArray( val ) ) {
8245				val = jQuery.map( val, function( value ) {
8246					return value == null ? "" : value + "";
8247				} );
8248			}
8249
8250			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
8251
8252			// If set returns undefined, fall back to normal setting
vendor: 4,648 bytes, lines 8253-8443
8253			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
8254				this.value = val;
8255			}
8256		} );
8257	}
8258} );
8259
8260jQuery.extend( {
8261	valHooks: {
8262		option: {
8263			get: function( elem ) {
8264				var val = jQuery.find.attr( elem, "value" );
8265				return val != null ?
8266					val :
8267
8268					// Support: IE10-11+
8269					// option.text throws exceptions (#14686, #14858)
8270					// Strip and collapse whitespace
8271					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
8272					jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " );
8273			}
8274		},
8275		select: {
8276			get: function( elem ) {
8277				var value, option,
8278					options = elem.options,
8279					index = elem.selectedIndex,
8280					one = elem.type === "select-one" || index < 0,
8281					values = one ? null : [],
8282					max = one ? index + 1 : options.length,
8283					i = index < 0 ?
8284						max :
8285						one ? index : 0;
8286
8287				// Loop through all the selected options
8288				for ( ; i < max; i++ ) {
8289					option = options[ i ];
8290
8291					// oldIE doesn't update selected after form reset (#2551)
8292					if ( ( option.selected || i === index ) &&
8293
8294							// Don't return options that are disabled or in a disabled optgroup
8295							( support.optDisabled ?
8296								!option.disabled :
8297								option.getAttribute( "disabled" ) === null ) &&
8298							( !option.parentNode.disabled ||
8299								!jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
8300
8301						// Get the specific value for the option
8302						value = jQuery( option ).val();
8303
8304						// We don't need an array for one selects
8305						if ( one ) {
8306							return value;
8307						}
8308
8309						// Multi-Selects return an array
8310						values.push( value );
8311					}
8312				}
8313
8314				return values;
8315			},
8316
8317			set: function( elem, value ) {
8318				var optionSet, option,
8319					options = elem.options,
8320					values = jQuery.makeArray( value ),
8321					i = options.length;
8322
8323				while ( i-- ) {
8324					option = options[ i ];
8325
8326					if ( jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) {
8327
8328						// Support: IE6
8329						// When new option element is added to select box we need to
8330						// force reflow of newly added node in order to workaround delay
8331						// of initialization properties
8332						try {
8333							option.selected = optionSet = true;
8334
8335						} catch ( _ ) {
8336
8337							// Will be executed only in IE6
8338							option.scrollHeight;
8339						}
8340
8341					} else {
8342						option.selected = false;
8343					}
8344				}
8345
8346				// Force browsers to behave consistently when non-matching value is set
8347				if ( !optionSet ) {
8348					elem.selectedIndex = -1;
8349				}
8350
8351				return options;
8352			}
8353		}
8354	}
8355} );
8356
8357// Radios and checkboxes getter/setter
8358jQuery.each( [ "radio", "checkbox" ], function() {
8359	jQuery.valHooks[ this ] = {
8360		set: function( elem, value ) {
8361			if ( jQuery.isArray( value ) ) {
8362				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
8363			}
8364		}
8365	};
8366	if ( !support.checkOn ) {
8367		jQuery.valHooks[ this ].get = function( elem ) {
8368			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
8369		};
8370	}
8371} );
8372
8373
8374
8375
8376var nodeHook, boolHook,
8377	attrHandle = jQuery.expr.attrHandle,
8378	ruseDefault = /^(?:checked|selected)$/i,
8379	getSetAttribute = support.getSetAttribute,
8380	getSetInput = support.input;
8381
8382jQuery.fn.extend( {
8383	attr: function( name, value ) {
8384		return access( this, jQuery.attr, name, value, arguments.length > 1 );
8385	},
8386
8387	removeAttr: function( name ) {
8388		return this.each( function() {
8389			jQuery.removeAttr( this, name );
8390		} );
8391	}
8392} );
8393
8394jQuery.extend( {
8395	attr: function( elem, name, value ) {
8396		var ret, hooks,
8397			nType = elem.nodeType;
8398
8399		// Don't get/set attributes on text, comment and attribute nodes
8400		if ( nType === 3 || nType === 8 || nType === 2 ) {
8401			return;
8402		}
8403
8404		// Fallback to prop when attributes are not supported
8405		if ( typeof elem.getAttribute === "undefined" ) {
8406			return jQuery.prop( elem, name, value );
8407		}
8408
8409		// All attributes are lowercase
8410		// Grab necessary hook if one is defined
8411		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
8412			name = name.toLowerCase();
8413			hooks = jQuery.attrHooks[ name ] ||
8414				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
8415		}
8416
8417		if ( value !== undefined ) {
8418			if ( value === null ) {
8419				jQuery.removeAttr( elem, name );
8420				return;
8421			}
8422
8423			if ( hooks && "set" in hooks &&
8424				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
8425				return ret;
8426			}
8427
8428			elem.setAttribute( name, value + "" );
8429			return value;
8430		}
8431
8432		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
8433			return ret;
8434		}
8435
8436		ret = jQuery.find.attr( elem, name );
8437
8438		// Non-existent attributes return null, we normalize to undefined
8439		return ret == null ? undefined : ret;
8440	},
8441
8442	attrHooks: {
8443		type: {
vendor: 4,457 bytes, lines 8444-8610
8444			set: function( elem, value ) {
8445				if ( !support.radioValue && value === "radio" &&
8446					jQuery.nodeName( elem, "input" ) ) {
8447
8448					// Setting the type on a radio button after the value resets the value in IE8-9
8449					// Reset value to default in case type is set after value during creation
8450					var val = elem.value;
8451					elem.setAttribute( "type", value );
8452					if ( val ) {
8453						elem.value = val;
8454					}
8455					return value;
8456				}
8457			}
8458		}
8459	},
8460
8461	removeAttr: function( elem, value ) {
8462		var name, propName,
8463			i = 0,
8464			attrNames = value && value.match( rnotwhite );
8465
8466		if ( attrNames && elem.nodeType === 1 ) {
8467			while ( ( name = attrNames[ i++ ] ) ) {
8468				propName = jQuery.propFix[ name ] || name;
8469
8470				// Boolean attributes get special treatment (#10870)
8471				if ( jQuery.expr.match.bool.test( name ) ) {
8472
8473					// Set corresponding property to false
8474					if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
8475						elem[ propName ] = false;
8476
8477					// Support: IE<9
8478					// Also clear defaultChecked/defaultSelected (if appropriate)
8479					} else {
8480						elem[ jQuery.camelCase( "default-" + name ) ] =
8481							elem[ propName ] = false;
8482					}
8483
8484				// See #9699 for explanation of this approach (setting first, then removal)
8485				} else {
8486					jQuery.attr( elem, name, "" );
8487				}
8488
8489				elem.removeAttribute( getSetAttribute ? name : propName );
8490			}
8491		}
8492	}
8493} );
8494
8495// Hooks for boolean attributes
8496boolHook = {
8497	set: function( elem, value, name ) {
8498		if ( value === false ) {
8499
8500			// Remove boolean attributes when set to false
8501			jQuery.removeAttr( elem, name );
8502		} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
8503
8504			// IE<8 needs the *property* name
8505			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
8506
8507		} else {
8508
8509			// Support: IE<9
8510			// Use defaultChecked and defaultSelected for oldIE
8511			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
8512		}
8513		return name;
8514	}
8515};
8516
8517jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
8518	var getter = attrHandle[ name ] || jQuery.find.attr;
8519
8520	if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
8521		attrHandle[ name ] = function( elem, name, isXML ) {
8522			var ret, handle;
8523			if ( !isXML ) {
8524
8525				// Avoid an infinite loop by temporarily removing this function from the getter
8526				handle = attrHandle[ name ];
8527				attrHandle[ name ] = ret;
8528				ret = getter( elem, name, isXML ) != null ?
8529					name.toLowerCase() :
8530					null;
8531				attrHandle[ name ] = handle;
8532			}
8533			return ret;
8534		};
8535	} else {
8536		attrHandle[ name ] = function( elem, name, isXML ) {
8537			if ( !isXML ) {
8538				return elem[ jQuery.camelCase( "default-" + name ) ] ?
8539					name.toLowerCase() :
8540					null;
8541			}
8542		};
8543	}
8544} );
8545
8546// fix oldIE attroperties
8547if ( !getSetInput || !getSetAttribute ) {
8548	jQuery.attrHooks.value = {
8549		set: function( elem, value, name ) {
8550			if ( jQuery.nodeName( elem, "input" ) ) {
8551
8552				// Does not return so that setAttribute is also used
8553				elem.defaultValue = value;
8554			} else {
8555
8556				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
8557				return nodeHook && nodeHook.set( elem, value, name );
8558			}
8559		}
8560	};
8561}
8562
8563// IE6/7 do not support getting/setting some attributes with get/setAttribute
8564if ( !getSetAttribute ) {
8565
8566	// Use this for any attribute in IE6/7
8567	// This fixes almost every IE6/7 issue
8568	nodeHook = {
8569		set: function( elem, value, name ) {
8570
8571			// Set the existing or create a new attribute node
8572			var ret = elem.getAttributeNode( name );
8573			if ( !ret ) {
8574				elem.setAttributeNode(
8575					( ret = elem.ownerDocument.createAttribute( name ) )
8576				);
8577			}
8578
8579			ret.value = value += "";
8580
8581			// Break association with cloned elements by also using setAttribute (#9646)
8582			if ( name === "value" || value === elem.getAttribute( name ) ) {
8583				return value;
8584			}
8585		}
8586	};
8587
8588	// Some attributes are constructed with empty-string values when not defined
8589	attrHandle.id = attrHandle.name = attrHandle.coords =
8590		function( elem, name, isXML ) {
8591			var ret;
8592			if ( !isXML ) {
8593				return ( ret = elem.getAttributeNode( name ) ) && ret.value !== "" ?
8594					ret.value :
8595					null;
8596			}
8597		};
8598
8599	// Fixing value retrieval on a button requires this module
8600	jQuery.valHooks.button = {
8601		get: function( elem, name ) {
8602			var ret = elem.getAttributeNode( name );
8603			if ( ret && ret.specified ) {
8604				return ret.value;
8605			}
8606		},
8607		set: nodeHook.set
8608	};
8609
8610	// Set contenteditable to false on removals(#10429)
8611	// Setting to empty string throws an error as an invalid value
8612	jQuery.attrHooks.contenteditable = {
8613		set: function( elem, value, name ) {
8614			nodeHook.set( elem, value === "" ? false : value, name );
8615		}
8616	};
8617
8618	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
8619	// This is for removals
8620	jQuery.each( [ "width", "height" ], function( i, name ) {
8621		jQuery.attrHooks[ name ] = {
8622			set: function( elem, value ) {
8623				if ( value === "" ) {
8624					elem.setAttribute( name, "auto" );
8625					return value;
8626				}
8627			}
8628		};
8629	} );
8630}
8631
8632if ( !support.style ) {
8633	jQuery.attrHooks.style = {
8634		get: function( elem ) {
8635
8636			// Return undefined in the case of empty string
8637			// Note: IE uppercases css property names, but if we were to .toLowerCase()
8638			// .cssText, that would destroy case sensitivity in URL's, like in "background"
8639			return elem.style.cssText || undefined;
8640		},
8641		set: function( elem, value ) {
8642			return ( elem.style.cssText = value + "" );
8643		}
8644	};
8645}
8646
8647
8648
8649
8650var rfocusable = /^(?:input|select|textarea|button|object)$/i,
8651	rclickable = /^(?:a|area)$/i;
8652
8653jQuery.fn.extend( {
8654	prop: function( name, value ) {
8655		return access( this, jQuery.prop, name, value, arguments.length > 1 );
8656	},
8657
8658	removeProp: function( name ) {
8659		name = jQuery.propFix[ name ] || name;
8660		return this.each( function() {
8661
8662			// try/catch handles cases where IE balks (such as removing a property on window)
8663			try {
8664				this[ name ] = undefined;
8665				delete this[ name ];
8666			} catch ( e ) {}
8667		} );
8668	}
8669} );
8670
8671jQuery.extend( {
8672	prop: function( elem, name, value ) {
8673		var ret, hooks,
8674			nType = elem.nodeType;
8675
8676		// Don't get/set properties on text, comment and attribute nodes
8677		if ( nType === 3 || nType === 8 || nType === 2 ) {
8678			return;
8679		}
8680
8681		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
8682
8683			// Fix name and attach hooks
8684			name = jQuery.propFix[ name ] || name;
8685			hooks = jQuery.propHooks[ name ];
8686		}
8687
8688		if ( value !== undefined ) {
8689			if ( hooks && "set" in hooks &&
8690				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
8691				return ret;
8692			}
8693
8694			return ( elem[ name ] = value );
8695		}
8696
8697		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
8698			return ret;
8699		}
8700
8701		return elem[ name ];
8702	},
8703
8704	propHooks: {
8705		tabIndex: {
8706			get: function( elem ) {
8707
8708				// elem.tabIndex doesn't always return the
8709				// correct value when it hasn't been explicitly set
8710				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
8711				// Use proper attribute retrieval(#12072)
8712				var tabindex = jQuery.find.attr( elem, "tabindex" );
8713
8714				return tabindex ?
8715					parseInt( tabindex, 10 ) :
8716					rfocusable.test( elem.nodeName ) ||
8717						rclickable.test( elem.nodeName ) && elem.href ?
8718							0 :
8719							-1;
8720			}
8721		}
8722	},
8723
8724	propFix: {
8725		"for": "htmlFor",
8726		"class": "className"
8727	}
8728} );
8729
8730// Some attributes require a special call on IE
8731// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
8732if ( !support.hrefNormalized ) {
8733
8734	// href/src property should get the full normalized URL (#10299/#12915)
8735	jQuery.each( [ "href", "src" ], function( i, name ) {
8736		jQuery.propHooks[ name ] = {
8737			get: function( elem ) {
8738				return elem.getAttribute( name, 4 );
8739			}
8740		};
8741	} );
8742}
8743
8744// Support: Safari, IE9+
8745// Accessing the selectedIndex property
8746// forces the browser to respect setting selected
8747// on the option
8748// The getter ensures a default option is selected
8749// when in an optgroup
8750if ( !support.optSelected ) {
8751	jQuery.propHooks.selected = {
8752		get: function( elem ) {
8753			var parent = elem.parentNode;
8754
8755			if ( parent ) {
8756				parent.selectedIndex;
8757
8758				// Make sure that it also works with optgroups, see #5701
8759				if ( parent.parentNode ) {
8760					parent.parentNode.selectedIndex;
8761				}
8762			}
8763			return null;
8764		},
8765		set: function( elem ) {
8766			var parent = elem.parentNode;
8767			if ( parent ) {
8768				parent.selectedIndex;
8769
8770				if ( parent.parentNode ) {
8771					parent.parentNode.selectedIndex;
8772				}
8773			}
8774		}
8775	};
8776}
8777
8778jQuery.each( [
8779	"tabIndex",
8780	"readOnly",
8781	"maxLength",
8782	"cellSpacing",
8783	"cellPadding",
8784	"rowSpan",
8785	"colSpan",
8786	"useMap",
8787	"frameBorder",
8788	"contentEditable"
8789], function() {
8790	jQuery.propFix[ this.toLowerCase() ] = this;
8791} );
8792
8793// IE6/7 call enctype encoding
8794if ( !support.enctype ) {
8795	jQuery.propFix.enctype = "encoding";
8796}
8797
8798
8799
8800
8801var rclass = /[\t\r\n\f]/g;
8802
8803function getClass( elem ) {
8804	return jQuery.attr( elem, "class" ) || "";
8805}
8806
8807jQuery.fn.extend( {
8808	addClass: function( value ) {
8809		var classes, elem, cur, curValue, clazz, j, finalValue,
8810			i = 0;
8811
8812		if ( jQuery.isFunction( value ) ) {
8813			return this.each( function( j ) {
8814				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
8815			} );
8816		}
8817
8818		if ( typeof value === "string" && value ) {
8819			classes = value.match( rnotwhite ) || [];
8820
8821			while ( ( elem = this[ i++ ] ) ) {
8822				curValue = getClass( elem );
8823				cur = elem.nodeType === 1 &&
8824					( " " + curValue + " " ).replace( rclass, " " );
8825
8826				if ( cur ) {
8827					j = 0;
8828					while ( ( clazz = classes[ j++ ] ) ) {
8829						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
8830							cur += clazz + " ";
8831						}
8832					}
8833
8834					// only assign if different to avoid unneeded rendering.
8835					finalValue = jQuery.trim( cur );
8836					if ( curValue !== finalValue ) {
8837						jQuery.attr( elem, "class", finalValue );
8838					}
8839				}
8840			}
8841		}
8842
8843		return this;
8844	},
8845
8846	removeClass: function( value ) {
8847		var classes, elem, cur, curValue, clazz, j, finalValue,
8848			i = 0;
8849
8850		if ( jQuery.isFunction( value ) ) {
8851			return this.each( function( j ) {
8852				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
8853			} );
8854		}
8855
8856		if ( !arguments.length ) {
8857			return this.attr( "class", "" );
8858		}
8859
8860		if ( typeof value === "string" && value ) {
8861			classes = value.match( rnotwhite ) || [];
8862
8863			while ( ( elem = this[ i++ ] ) ) {
8864				curValue = getClass( elem );
8865
8866				// This expression is here for better compressibility (see addClass)
8867				cur = elem.nodeType === 1 &&
8868					( " " + curValue + " " ).replace( rclass, " " );
8869
8870				if ( cur ) {
8871					j = 0;
8872					while ( ( clazz = classes[ j++ ] ) ) {
8873
8874						// Remove *all* instances
8875						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
8876							cur = cur.replace( " " + clazz + " ", " " );
8877						}
8878					}
8879
8880					// Only assign if different to avoid unneeded rendering.
8881					finalValue = jQuery.trim( cur );
8882					if ( curValue !== finalValue ) {
8883						jQuery.attr( elem, "class", finalValue );
8884					}
8885				}
8886			}
8887		}
8888
8889		return this;
8890	},
8891
8892	toggleClass: function( value, stateVal ) {
8893		var type = typeof value;
8894
8895		if ( typeof stateVal === "boolean" && type === "string" ) {
8896			return stateVal ? this.addClass( value ) : this.removeClass( value );
8897		}
8898
8899		if ( jQuery.isFunction( value ) ) {
8900			return this.each( function( i ) {
8901				jQuery( this ).toggleClass(
8902					value.call( this, i, getClass( this ), stateVal ),
8903					stateVal
8904				);
8905			} );
8906		}
8907
8908		return this.each( function() {
8909			var className, i, self, classNames;
8910
8911			if ( type === "string" ) {
8912
8913				// Toggle individual class names
8914				i = 0;
8915				self = jQuery( this );
8916				classNames = value.match( rnotwhite ) || [];
8917
8918				while ( ( className = classNames[ i++ ] ) ) {
8919
8920					// Check each className given, space separated list
8921					if ( self.hasClass( className ) ) {
8922						self.removeClass( className );
8923					} else {
8924						self.addClass( className );
8925					}
8926				}
8927
8928			// Toggle whole class name
8929			} else if ( value === undefined || type === "boolean" ) {
8930				className = getClass( this );
8931				if ( className ) {
8932
8933					// store className if set
8934					jQuery._data( this, "__className__", className );
8935				}
8936
8937				// If the element has a class name or if we're passed "false",
8938				// then remove the whole classname (if there was one, the above saved it).
8939				// Otherwise bring back whatever was previously saved (if anything),
8940				// falling back to the empty string if nothing was stored.
8941				jQuery.attr( this, "class",
8942					className || value === false ?
8943					"" :
8944					jQuery._data( this, "__className__" ) || ""
8945				);
8946			}
8947		} );
8948	},
8949
8950	hasClass: function( selector ) {
8951		var className, elem,
8952			i = 0;
8953
8954		className = " " + selector + " ";
8955		while ( ( elem = this[ i++ ] ) ) {
8956			if ( elem.nodeType === 1 &&
8957				( " " + getClass( elem ) + " " ).replace( rclass, " " )
8958					.indexOf( className ) > -1
8959			) {
8960				return true;
8961			}
8962		}
8963
8964		return false;
8965	}
8966} );
8967
8968
8969
8970
8971// Return jQuery for attributes-only inclusion
8972
8973
8974jQuery.each( ( "blur focus focusin focusout load resize scroll unload click dblclick " +
8975	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
8976	"change select submit keydown keypress keyup error contextmenu" ).split( " " ),
8977	function( i, name ) {
8978
8979	// Handle event binding
8980	jQuery.fn[ name ] = function( data, fn ) {
8981		return arguments.length > 0 ?
8982			this.on( name, null, data, fn ) :
8983			this.trigger( name );
8984	};
8985} );
8986
8987jQuery.fn.extend( {
8988	hover: function( fnOver, fnOut ) {
8989		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
8990	}
8991} );
8992
8993
8994var location = window.location;
8995
8996var nonce = jQuery.now();
8997
8998var rquery = ( /\?/ );
8999
9000
9001
9002var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
9003
9004jQuery.parseJSON = function( data ) {
9005
9006	// Attempt to parse using the native JSON parser first
9007	if ( window.JSON && window.JSON.parse ) {
9008
9009		// Support: Android 2.3
9010		// Workaround failure to string-cast null input
9011		return window.JSON.parse( data + "" );
9012	}
9013
9014	var requireNonComma,
9015		depth = null,
9016		str = jQuery.trim( data + "" );
9017
9018	// Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
9019	// after removing valid tokens
9020	return str && !jQuery.trim( str.replace( rvalidtokens, function( token, comma, open, close ) {
9021
9022		// Force termination if we see a misplaced comma
9023		if ( requireNonComma && comma ) {
9024			depth = 0;
9025		}
9026
9027		// Perform no more replacements after returning to outermost depth
9028		if ( depth === 0 ) {
9029			return token;
9030		}
9031
9032		// Commas must not follow "[", "{", or ","
9033		requireNonComma = open || comma;
9034
9035		// Determine new depth
9036		// array/object open ("[" or "{"): depth += true - false (increment)
9037		// array/object close ("]" or "}"): depth += false - true (decrement)
9038		// other cases ("," or primitive): depth += true - true (numeric cast)
9039		depth += !close - !open;
9040
9041		// Remove this token
9042		return "";
9043	} ) ) ?
9044		( Function( "return " + str ) )() :
9045		jQuery.error( "Invalid JSON: " + data );
9046};
9047
9048
9049// Cross-browser xml parsing
vendor: 10,393 bytes, lines 9050-9455
9050jQuery.parseXML = function( data ) {
9051	var xml, tmp;
9052	if ( !data || typeof data !== "string" ) {
9053		return null;
9054	}
9055	try {
9056		if ( window.DOMParser ) { // Standard
9057			tmp = new window.DOMParser();
9058			xml = tmp.parseFromString( data, "text/xml" );
9059		} else { // IE
9060			xml = new window.ActiveXObject( "Microsoft.XMLDOM" );
9061			xml.async = "false";
9062			xml.loadXML( data );
9063		}
9064	} catch ( e ) {
9065		xml = undefined;
9066	}
9067	if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
9068		jQuery.error( "Invalid XML: " + data );
9069	}
9070	return xml;
9071};
9072
9073
9074var
9075	rhash = /#.*$/,
9076	rts = /([?&])_=[^&]*/,
9077
9078	// IE leaves an \r character at EOL
9079	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg,
9080
9081	// #7653, #8125, #8152: local protocol detection
9082	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
9083	rnoContent = /^(?:GET|HEAD)$/,
9084	rprotocol = /^\/\//,
9085	rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,
9086
9087	/* Prefilters
9088	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
9089	 * 2) These are called:
9090	 *    - BEFORE asking for a transport
9091	 *    - AFTER param serialization (s.data is a string if s.processData is true)
9092	 * 3) key is the dataType
9093	 * 4) the catchall symbol "*" can be used
9094	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
9095	 */
9096	prefilters = {},
9097
9098	/* Transports bindings
9099	 * 1) key is the dataType
9100	 * 2) the catchall symbol "*" can be used
9101	 * 3) selection will start with transport dataType and THEN go to "*" if needed
9102	 */
9103	transports = {},
9104
9105	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
9106	allTypes = "*/".concat( "*" ),
9107
9108	// Document location
9109	ajaxLocation = location.href,
9110
9111	// Segment location into parts
9112	ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
9113
9114// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
9115function addToPrefiltersOrTransports( structure ) {
9116
9117	// dataTypeExpression is optional and defaults to "*"
9118	return function( dataTypeExpression, func ) {
9119
9120		if ( typeof dataTypeExpression !== "string" ) {
9121			func = dataTypeExpression;
9122			dataTypeExpression = "*";
9123		}
9124
9125		var dataType,
9126			i = 0,
9127			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
9128
9129		if ( jQuery.isFunction( func ) ) {
9130
9131			// For each dataType in the dataTypeExpression
9132			while ( ( dataType = dataTypes[ i++ ] ) ) {
9133
9134				// Prepend if requested
9135				if ( dataType.charAt( 0 ) === "+" ) {
9136					dataType = dataType.slice( 1 ) || "*";
9137					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
9138
9139				// Otherwise append
9140				} else {
9141					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
9142				}
9143			}
9144		}
9145	};
9146}
9147
9148// Base inspection function for prefilters and transports
9149function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
9150
9151	var inspected = {},
9152		seekingTransport = ( structure === transports );
9153
9154	function inspect( dataType ) {
9155		var selected;
9156		inspected[ dataType ] = true;
9157		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
9158			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
9159			if ( typeof dataTypeOrTransport === "string" &&
9160				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
9161
9162				options.dataTypes.unshift( dataTypeOrTransport );
9163				inspect( dataTypeOrTransport );
9164				return false;
9165			} else if ( seekingTransport ) {
9166				return !( selected = dataTypeOrTransport );
9167			}
9168		} );
9169		return selected;
9170	}
9171
9172	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
9173}
9174
9175// A special extend for ajax options
9176// that takes "flat" options (not to be deep extended)
9177// Fixes #9887
9178function ajaxExtend( target, src ) {
9179	var deep, key,
9180		flatOptions = jQuery.ajaxSettings.flatOptions || {};
9181
9182	for ( key in src ) {
9183		if ( src[ key ] !== undefined ) {
9184			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
9185		}
9186	}
9187	if ( deep ) {
9188		jQuery.extend( true, target, deep );
9189	}
9190
9191	return target;
9192}
9193
9194/* Handles responses to an ajax request:
9195 * - finds the right dataType (mediates between content-type and expected dataType)
9196 * - returns the corresponding response
9197 */
9198function ajaxHandleResponses( s, jqXHR, responses ) {
9199	var firstDataType, ct, finalDataType, type,
9200		contents = s.contents,
9201		dataTypes = s.dataTypes;
9202
9203	// Remove auto dataType and get content-type in the process
9204	while ( dataTypes[ 0 ] === "*" ) {
9205		dataTypes.shift();
9206		if ( ct === undefined ) {
9207			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
9208		}
9209	}
9210
9211	// Check if we're dealing with a known content-type
9212	if ( ct ) {
9213		for ( type in contents ) {
9214			if ( contents[ type ] && contents[ type ].test( ct ) ) {
9215				dataTypes.unshift( type );
9216				break;
9217			}
9218		}
9219	}
9220
9221	// Check to see if we have a response for the expected dataType
9222	if ( dataTypes[ 0 ] in responses ) {
9223		finalDataType = dataTypes[ 0 ];
9224	} else {
9225
9226		// Try convertible dataTypes
9227		for ( type in responses ) {
9228			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
9229				finalDataType = type;
9230				break;
9231			}
9232			if ( !firstDataType ) {
9233				firstDataType = type;
9234			}
9235		}
9236
9237		// Or just use first one
9238		finalDataType = finalDataType || firstDataType;
9239	}
9240
9241	// If we found a dataType
9242	// We add the dataType to the list if needed
9243	// and return the corresponding response
9244	if ( finalDataType ) {
9245		if ( finalDataType !== dataTypes[ 0 ] ) {
9246			dataTypes.unshift( finalDataType );
9247		}
9248		return responses[ finalDataType ];
9249	}
9250}
9251
9252/* Chain conversions given the request and the original response
9253 * Also sets the responseXXX fields on the jqXHR instance
9254 */
9255function ajaxConvert( s, response, jqXHR, isSuccess ) {
9256	var conv2, current, conv, tmp, prev,
9257		converters = {},
9258
9259		// Work with a copy of dataTypes in case we need to modify it for conversion
9260		dataTypes = s.dataTypes.slice();
9261
9262	// Create converters map with lowercased keys
9263	if ( dataTypes[ 1 ] ) {
9264		for ( conv in s.converters ) {
9265			converters[ conv.toLowerCase() ] = s.converters[ conv ];
9266		}
9267	}
9268
9269	current = dataTypes.shift();
9270
9271	// Convert to each sequential dataType
9272	while ( current ) {
9273
9274		if ( s.responseFields[ current ] ) {
9275			jqXHR[ s.responseFields[ current ] ] = response;
9276		}
9277
9278		// Apply the dataFilter if provided
9279		if ( !prev && isSuccess && s.dataFilter ) {
9280			response = s.dataFilter( response, s.dataType );
9281		}
9282
9283		prev = current;
9284		current = dataTypes.shift();
9285
9286		if ( current ) {
9287
9288			// There's only work to do if current dataType is non-auto
9289			if ( current === "*" ) {
9290
9291				current = prev;
9292
9293			// Convert response if prev dataType is non-auto and differs from current
9294			} else if ( prev !== "*" && prev !== current ) {
9295
9296				// Seek a direct converter
9297				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
9298
9299				// If none found, seek a pair
9300				if ( !conv ) {
9301					for ( conv2 in converters ) {
9302
9303						// If conv2 outputs current
9304						tmp = conv2.split( " " );
9305						if ( tmp[ 1 ] === current ) {
9306
9307							// If prev can be converted to accepted input
9308							conv = converters[ prev + " " + tmp[ 0 ] ] ||
9309								converters[ "* " + tmp[ 0 ] ];
9310							if ( conv ) {
9311
9312								// Condense equivalence converters
9313								if ( conv === true ) {
9314									conv = converters[ conv2 ];
9315
9316								// Otherwise, insert the intermediate dataType
9317								} else if ( converters[ conv2 ] !== true ) {
9318									current = tmp[ 0 ];
9319									dataTypes.unshift( tmp[ 1 ] );
9320								}
9321								break;
9322							}
9323						}
9324					}
9325				}
9326
9327				// Apply converter (if not an equivalence)
9328				if ( conv !== true ) {
9329
9330					// Unless errors are allowed to bubble, catch and return them
9331					if ( conv && s[ "throws" ] ) { // jscs:ignore requireDotNotation
9332						response = conv( response );
9333					} else {
9334						try {
9335							response = conv( response );
9336						} catch ( e ) {
9337							return {
9338								state: "parsererror",
9339								error: conv ? e : "No conversion from " + prev + " to " + current
9340							};
9341						}
9342					}
9343				}
9344			}
9345		}
9346	}
9347
9348	return { state: "success", data: response };
9349}
9350
9351jQuery.extend( {
9352
9353	// Counter for holding the number of active queries
9354	active: 0,
9355
9356	// Last-Modified header cache for next request
9357	lastModified: {},
9358	etag: {},
9359
9360	ajaxSettings: {
9361		url: ajaxLocation,
9362		type: "GET",
9363		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
9364		global: true,
9365		processData: true,
9366		async: true,
9367		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
9368		/*
9369		timeout: 0,
9370		data: null,
9371		dataType: null,
9372		username: null,
9373		password: null,
9374		cache: null,
9375		throws: false,
9376		traditional: false,
9377		headers: {},
9378		*/
9379
9380		accepts: {
9381			"*": allTypes,
9382			text: "text/plain",
9383			html: "text/html",
9384			xml: "application/xml, text/xml",
9385			json: "application/json, text/javascript"
9386		},
9387
9388		contents: {
9389			xml: /\bxml\b/,
9390			html: /\bhtml/,
9391			json: /\bjson\b/
9392		},
9393
9394		responseFields: {
9395			xml: "responseXML",
9396			text: "responseText",
9397			json: "responseJSON"
9398		},
9399
9400		// Data converters
9401		// Keys separate source (or catchall "*") and destination types with a single space
9402		converters: {
9403
9404			// Convert anything to text
9405			"* text": String,
9406
9407			// Text to html (true = no transformation)
9408			"text html": true,
9409
9410			// Evaluate text as a json expression
9411			"text json": jQuery.parseJSON,
9412
9413			// Parse text as xml
9414			"text xml": jQuery.parseXML
9415		},
9416
9417		// For options that shouldn't be deep extended:
9418		// you can add your own custom options here if
9419		// and when you create one that shouldn't be
9420		// deep extended (see ajaxExtend)
9421		flatOptions: {
9422			url: true,
9423			context: true
9424		}
9425	},
9426
9427	// Creates a full fledged settings object into target
9428	// with both ajaxSettings and settings fields.
9429	// If target is omitted, writes into ajaxSettings.
9430	ajaxSetup: function( target, settings ) {
9431		return settings ?
9432
9433			// Building a settings object
9434			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
9435
9436			// Extending ajaxSettings
9437			ajaxExtend( jQuery.ajaxSettings, target );
9438	},
9439
9440	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
9441	ajaxTransport: addToPrefiltersOrTransports( transports ),
9442
9443	// Main method
9444	ajax: function( url, options ) {
9445
9446		// If url is an object, simulate pre-1.5 signature
9447		if ( typeof url === "object" ) {
9448			options = url;
9449			url = undefined;
9450		}
9451
9452		// Force options to be an object
9453		options = options || {};
9454
9455		var
vendor: 5,146 bytes, lines 9456-9646
9456
9457			// Cross-domain detection vars
9458			parts,
9459
9460			// Loop variable
9461			i,
9462
9463			// URL without anti-cache param
9464			cacheURL,
9465
9466			// Response headers as string
9467			responseHeadersString,
9468
9469			// timeout handle
9470			timeoutTimer,
9471
9472			// To know if global events are to be dispatched
9473			fireGlobals,
9474
9475			transport,
9476
9477			// Response headers
9478			responseHeaders,
9479
9480			// Create the final options object
9481			s = jQuery.ajaxSetup( {}, options ),
9482
9483			// Callbacks context
9484			callbackContext = s.context || s,
9485
9486			// Context for global events is callbackContext if it is a DOM node or jQuery collection
9487			globalEventContext = s.context &&
9488				( callbackContext.nodeType || callbackContext.jquery ) ?
9489					jQuery( callbackContext ) :
9490					jQuery.event,
9491
9492			// Deferreds
9493			deferred = jQuery.Deferred(),
9494			completeDeferred = jQuery.Callbacks( "once memory" ),
9495
9496			// Status-dependent callbacks
9497			statusCode = s.statusCode || {},
9498
9499			// Headers (they are sent all at once)
9500			requestHeaders = {},
9501			requestHeadersNames = {},
9502
9503			// The jqXHR state
9504			state = 0,
9505
9506			// Default abort message
9507			strAbort = "canceled",
9508
9509			// Fake xhr
9510			jqXHR = {
9511				readyState: 0,
9512
9513				// Builds headers hashtable if needed
9514				getResponseHeader: function( key ) {
9515					var match;
9516					if ( state === 2 ) {
9517						if ( !responseHeaders ) {
9518							responseHeaders = {};
9519							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
9520								responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
9521							}
9522						}
9523						match = responseHeaders[ key.toLowerCase() ];
9524					}
9525					return match == null ? null : match;
9526				},
9527
9528				// Raw string
9529				getAllResponseHeaders: function() {
9530					return state === 2 ? responseHeadersString : null;
9531				},
9532
9533				// Caches the header
9534				setRequestHeader: function( name, value ) {
9535					var lname = name.toLowerCase();
9536					if ( !state ) {
9537						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
9538						requestHeaders[ name ] = value;
9539					}
9540					return this;
9541				},
9542
9543				// Overrides response content-type header
9544				overrideMimeType: function( type ) {
9545					if ( !state ) {
9546						s.mimeType = type;
9547					}
9548					return this;
9549				},
9550
9551				// Status-dependent callbacks
9552				statusCode: function( map ) {
9553					var code;
9554					if ( map ) {
9555						if ( state < 2 ) {
9556							for ( code in map ) {
9557
9558								// Lazy-add the new callback in a way that preserves old ones
9559								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
9560							}
9561						} else {
9562
9563							// Execute the appropriate callbacks
9564							jqXHR.always( map[ jqXHR.status ] );
9565						}
9566					}
9567					return this;
9568				},
9569
9570				// Cancel the request
9571				abort: function( statusText ) {
9572					var finalText = statusText || strAbort;
9573					if ( transport ) {
9574						transport.abort( finalText );
9575					}
9576					done( 0, finalText );
9577					return this;
9578				}
9579			};
9580
9581		// Attach deferreds
9582		deferred.promise( jqXHR ).complete = completeDeferred.add;
9583		jqXHR.success = jqXHR.done;
9584		jqXHR.error = jqXHR.fail;
9585
9586		// Remove hash character (#7531: and string promotion)
9587		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
9588		// Handle falsy url in the settings object (#10093: consistency with old signature)
9589		// We also use the url parameter if available
9590		s.url = ( ( url || s.url || ajaxLocation ) + "" )
9591			.replace( rhash, "" )
9592			.replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
9593
9594		// Alias method option to type as per ticket #12004
9595		s.type = options.method || options.type || s.method || s.type;
9596
9597		// Extract dataTypes list
9598		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
9599
9600		// A cross-domain request is in order when we have a protocol:host:port mismatch
9601		if ( s.crossDomain == null ) {
9602			parts = rurl.exec( s.url.toLowerCase() );
9603			s.crossDomain = !!( parts &&
9604				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
9605					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
9606						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
9607			);
9608		}
9609
9610		// Convert data if not already a string
9611		if ( s.data && s.processData && typeof s.data !== "string" ) {
9612			s.data = jQuery.param( s.data, s.traditional );
9613		}
9614
9615		// Apply prefilters
9616		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
9617
9618		// If request was aborted inside a prefilter, stop there
9619		if ( state === 2 ) {
9620			return jqXHR;
9621		}
9622
9623		// We can fire global events as of now if asked to
9624		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
9625		fireGlobals = jQuery.event && s.global;
9626
9627		// Watch for a new set of requests
9628		if ( fireGlobals && jQuery.active++ === 0 ) {
9629			jQuery.event.trigger( "ajaxStart" );
9630		}
9631
9632		// Uppercase the type
9633		s.type = s.type.toUpperCase();
9634
9635		// Determine if request has content
9636		s.hasContent = !rnoContent.test( s.type );
9637
9638		// Save the URL in case we're toying with the If-Modified-Since
9639		// and/or If-None-Match header later on
9640		cacheURL = s.url;
9641
9642		// More options handling for requests with no content
9643		if ( !s.hasContent ) {
9644
9645			// If data is available, append data to url
9646			if ( s.data ) {
vendor: 8,244 bytes, lines 9647-9986
9647				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
9648
9649				// #9682: remove data so that it's not used in an eventual retry
9650				delete s.data;
9651			}
9652
9653			// Add anti-cache in url if needed
9654			if ( s.cache === false ) {
9655				s.url = rts.test( cacheURL ) ?
9656
9657					// If there is already a '_' parameter, set its value
9658					cacheURL.replace( rts, "$1_=" + nonce++ ) :
9659
9660					// Otherwise add one to the end
9661					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
9662			}
9663		}
9664
9665		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
9666		if ( s.ifModified ) {
9667			if ( jQuery.lastModified[ cacheURL ] ) {
9668				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
9669			}
9670			if ( jQuery.etag[ cacheURL ] ) {
9671				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
9672			}
9673		}
9674
9675		// Set the correct header, if data is being sent
9676		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
9677			jqXHR.setRequestHeader( "Content-Type", s.contentType );
9678		}
9679
9680		// Set the Accepts header for the server, depending on the dataType
9681		jqXHR.setRequestHeader(
9682			"Accept",
9683			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
9684				s.accepts[ s.dataTypes[ 0 ] ] +
9685					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
9686				s.accepts[ "*" ]
9687		);
9688
9689		// Check for headers option
9690		for ( i in s.headers ) {
9691			jqXHR.setRequestHeader( i, s.headers[ i ] );
9692		}
9693
9694		// Allow custom headers/mimetypes and early abort
9695		if ( s.beforeSend &&
9696			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
9697
9698			// Abort if not done already and return
9699			return jqXHR.abort();
9700		}
9701
9702		// aborting is no longer a cancellation
9703		strAbort = "abort";
9704
9705		// Install callbacks on deferreds
9706		for ( i in { success: 1, error: 1, complete: 1 } ) {
9707			jqXHR[ i ]( s[ i ] );
9708		}
9709
9710		// Get transport
9711		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
9712
9713		// If no transport, we auto-abort
9714		if ( !transport ) {
9715			done( -1, "No Transport" );
9716		} else {
9717			jqXHR.readyState = 1;
9718
9719			// Send global event
9720			if ( fireGlobals ) {
9721				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
9722			}
9723
9724			// If request was aborted inside ajaxSend, stop there
9725			if ( state === 2 ) {
9726				return jqXHR;
9727			}
9728
9729			// Timeout
9730			if ( s.async && s.timeout > 0 ) {
9731				timeoutTimer = window.setTimeout( function() {
9732					jqXHR.abort( "timeout" );
9733				}, s.timeout );
9734			}
9735
9736			try {
9737				state = 1;
9738				transport.send( requestHeaders, done );
9739			} catch ( e ) {
9740
9741				// Propagate exception as error if not done
9742				if ( state < 2 ) {
9743					done( -1, e );
9744
9745				// Simply rethrow otherwise
9746				} else {
9747					throw e;
9748				}
9749			}
9750		}
9751
9752		// Callback for when everything is done
9753		function done( status, nativeStatusText, responses, headers ) {
9754			var isSuccess, success, error, response, modified,
9755				statusText = nativeStatusText;
9756
9757			// Called once
9758			if ( state === 2 ) {
9759				return;
9760			}
9761
9762			// State is "done" now
9763			state = 2;
9764
9765			// Clear timeout if it exists
9766			if ( timeoutTimer ) {
9767				window.clearTimeout( timeoutTimer );
9768			}
9769
9770			// Dereference transport for early garbage collection
9771			// (no matter how long the jqXHR object will be used)
9772			transport = undefined;
9773
9774			// Cache response headers
9775			responseHeadersString = headers || "";
9776
9777			// Set readyState
9778			jqXHR.readyState = status > 0 ? 4 : 0;
9779
9780			// Determine if successful
9781			isSuccess = status >= 200 && status < 300 || status === 304;
9782
9783			// Get response data
9784			if ( responses ) {
9785				response = ajaxHandleResponses( s, jqXHR, responses );
9786			}
9787
9788			// Convert no matter what (that way responseXXX fields are always set)
9789			response = ajaxConvert( s, response, jqXHR, isSuccess );
9790
9791			// If successful, handle type chaining
9792			if ( isSuccess ) {
9793
9794				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
9795				if ( s.ifModified ) {
9796					modified = jqXHR.getResponseHeader( "Last-Modified" );
9797					if ( modified ) {
9798						jQuery.lastModified[ cacheURL ] = modified;
9799					}
9800					modified = jqXHR.getResponseHeader( "etag" );
9801					if ( modified ) {
9802						jQuery.etag[ cacheURL ] = modified;
9803					}
9804				}
9805
9806				// if no content
9807				if ( status === 204 || s.type === "HEAD" ) {
9808					statusText = "nocontent";
9809
9810				// if not modified
9811				} else if ( status === 304 ) {
9812					statusText = "notmodified";
9813
9814				// If we have data, let's convert it
9815				} else {
9816					statusText = response.state;
9817					success = response.data;
9818					error = response.error;
9819					isSuccess = !error;
9820				}
9821			} else {
9822
9823				// We extract error from statusText
9824				// then normalize statusText and status for non-aborts
9825				error = statusText;
9826				if ( status || !statusText ) {
9827					statusText = "error";
9828					if ( status < 0 ) {
9829						status = 0;
9830					}
9831				}
9832			}
9833
9834			// Set data for the fake xhr object
9835			jqXHR.status = status;
9836			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
9837
9838			// Success/Error
9839			if ( isSuccess ) {
9840				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
9841			} else {
9842				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
9843			}
9844
9845			// Status-dependent callbacks
9846			jqXHR.statusCode( statusCode );
9847			statusCode = undefined;
9848
9849			if ( fireGlobals ) {
9850				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
9851					[ jqXHR, s, isSuccess ? success : error ] );
9852			}
9853
9854			// Complete
9855			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
9856
9857			if ( fireGlobals ) {
9858				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
9859
9860				// Handle the global AJAX counter
9861				if ( !( --jQuery.active ) ) {
9862					jQuery.event.trigger( "ajaxStop" );
9863				}
9864			}
9865		}
9866
9867		return jqXHR;
9868	},
9869
9870	getJSON: function( url, data, callback ) {
9871		return jQuery.get( url, data, callback, "json" );
9872	},
9873
9874	getScript: function( url, callback ) {
9875		return jQuery.get( url, undefined, callback, "script" );
9876	}
9877} );
9878
9879jQuery.each( [ "get", "post" ], function( i, method ) {
9880	jQuery[ method ] = function( url, data, callback, type ) {
9881
9882		// shift arguments if data argument was omitted
9883		if ( jQuery.isFunction( data ) ) {
9884			type = type || callback;
9885			callback = data;
9886			data = undefined;
9887		}
9888
9889		// The url can be an options object (which then must have .url)
9890		return jQuery.ajax( jQuery.extend( {
9891			url: url,
9892			type: method,
9893			dataType: type,
9894			data: data,
9895			success: callback
9896		}, jQuery.isPlainObject( url ) && url ) );
9897	};
9898} );
9899
9900
9901jQuery._evalUrl = function( url ) {
9902	return jQuery.ajax( {
9903		url: url,
9904
9905		// Make this explicit, since user can override this through ajaxSetup (#11264)
9906		type: "GET",
9907		dataType: "script",
9908		cache: true,
9909		async: false,
9910		global: false,
9911		"throws": true
9912	} );
9913};
9914
9915
9916jQuery.fn.extend( {
9917	wrapAll: function( html ) {
9918		if ( jQuery.isFunction( html ) ) {
9919			return this.each( function( i ) {
9920				jQuery( this ).wrapAll( html.call( this, i ) );
9921			} );
9922		}
9923
9924		if ( this[ 0 ] ) {
9925
9926			// The elements to wrap the target around
9927			var wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
9928
9929			if ( this[ 0 ].parentNode ) {
9930				wrap.insertBefore( this[ 0 ] );
9931			}
9932
9933			wrap.map( function() {
9934				var elem = this;
9935
9936				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
9937					elem = elem.firstChild;
9938				}
9939
9940				return elem;
9941			} ).append( this );
9942		}
9943
9944		return this;
9945	},
9946
9947	wrapInner: function( html ) {
9948		if ( jQuery.isFunction( html ) ) {
9949			return this.each( function( i ) {
9950				jQuery( this ).wrapInner( html.call( this, i ) );
9951			} );
9952		}
9953
9954		return this.each( function() {
9955			var self = jQuery( this ),
9956				contents = self.contents();
9957
9958			if ( contents.length ) {
9959				contents.wrapAll( html );
9960
9961			} else {
9962				self.append( html );
9963			}
9964		} );
9965	},
9966
9967	wrap: function( html ) {
9968		var isFunction = jQuery.isFunction( html );
9969
9970		return this.each( function( i ) {
9971			jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
9972		} );
9973	},
9974
9975	unwrap: function() {
9976		return this.parent().each( function() {
9977			if ( !jQuery.nodeName( this, "body" ) ) {
9978				jQuery( this ).replaceWith( this.childNodes );
9979			}
9980		} ).end();
9981	}
9982} );
9983
9984
9985function getDisplay( elem ) {
9986	return elem.style && elem.style.display || jQuery.css( elem, "dis
9986play" );
9987}
9988
9989function filterHidden( elem ) {
9990
9991	// Disconnected elements are considered hidden
9992	if ( !jQuery.contains( elem.ownerDocument || document, elem ) ) {
9993		return true;
9994	}
9995	while ( elem && elem.nodeType === 1 ) {
9996		if ( getDisplay( elem ) === "none" || elem.type === "hidden" ) {
9997			return true;
9998		}
9999		elem = elem.parentNode;
10000	}
10001	return false;
10002}
10003
10004jQuery.expr.filters.hidden = function( elem ) {
10005
10006	// Support: Opera <= 12.12
10007	// Opera reports offsetWidths and offsetHeights less than zero on some elements
10008	return support.reliableHiddenOffsets() ?
10009		( elem.offsetWidth <= 0 && elem.offsetHeight <= 0 &&
10010			!elem.getClientRects().length ) :
10011			filterHidden( elem );
10012};
10013
10014jQuery.expr.filters.visible = function( elem ) {
10015	return !jQuery.expr.filters.hidden( elem );
10016};
10017
10018
10019
10020
10021var r20 = /%20/g,
10022	rbracket = /\[\]$/,
10023	rCRLF = /\r?\n/g,
10024	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
10025	rsubmittable = /^(?:input|select|textarea|keygen)/i;
10026
10027function buildParams( prefix, obj, traditional, add ) {
10028	var name;
10029
10030	if ( jQuery.isArray( obj ) ) {
10031
10032		// Serialize array item.
10033		jQuery.each( obj, function( i, v ) {
10034			if ( traditional || rbracket.test( prefix ) ) {
10035
10036				// Treat each array item as a scalar.
10037				add( prefix, v );
10038
10039			} else {
10040
10041				// Item is non-scalar (array or object), encode its numeric index.
10042				buildParams(
10043					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
10044					v,
10045					traditional,
10046					add
10047				);
10048			}
10049		} );
10050
10051	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
10052
10053		// Serialize object item.
10054		for ( name in obj ) {
10055			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
10056		}
10057
10058	} else {
10059
10060		// Serialize scalar item.
10061		add( prefix, obj );
10062	}
10063}
10064
10065// Serialize an array of form elements or a set of
10066// key/values into a query string
10067jQuery.param = function( a, traditional ) {
10068	var prefix,
10069		s = [],
10070		add = function( key, value ) {
10071
10072			// If value is a function, invoke it and return its value
10073			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
10074			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
10075		};
10076
10077	// Set traditional to true for jQuery <= 1.3.2 behavior.
10078	if ( traditional === undefined ) {
10079		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
10080	}
10081
10082	// If an array was passed in, assume that it is an array of form elements.
10083	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
10084
10085		// Serialize the form elements
10086		jQuery.each( a, function() {
10087			add( this.name, this.value );
10088		} );
10089
10090	} else {
10091
10092		// If traditional, encode the "old" way (the way 1.3.2 or older
10093		// did it), otherwise encode params recursively.
10094		for ( prefix in a ) {
10095			buildParams( prefix, a[ prefix ], traditional, add );
10096		}
10097	}
10098
10099	// Return the resulting serialization
10100	return s.join( "&" ).replace( r20, "+" );
10101};
10102
10103jQuery.fn.extend( {
10104	serialize: function() {
10105		return jQuery.param( this.serializeArray() );
10106	},
10107	serializeArray: function() {
10108		return this.map( function() {
10109
10110			// Can add propHook for "elements" to filter or add form elements
10111			var elements = jQuery.prop( this, "elements" );
10112			return elements ? jQuery.makeArray( elements ) : this;
10113		} )
10114		.filter( function() {
10115			var type = this.type;
10116
10117			// Use .is(":disabled") so that fieldset[disabled] works
10118			return this.name && !jQuery( this ).is( ":disabled" ) &&
10119				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
10120				( this.checked || !rcheckableType.test( type ) );
10121		} )
10122		.map( function( i, elem ) {
10123			var val = jQuery( this ).val();
10124
10125			return val == null ?
10126				null :
10127				jQuery.isArray( val ) ?
10128					jQuery.map( val, function( val ) {
10129						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
10130					} ) :
10131					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
10132		} ).get();
10133	}
10134} );
10135
10136
10137// Create the request object
10138// (This is still attached to ajaxSettings for backward compatibility)
10139jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ?
10140
10141	// Support: IE6-IE8
10142	function() {
10143
10144		// XHR cannot access local files, always use ActiveX for that case
10145		if ( this.isLocal ) {
10146			return createActiveXHR();
10147		}
10148
vendor: 22,565 bytes, lines 10149-10969
10149		// Support: IE 9-11
10150		// IE seems to error on cross-domain PATCH requests when ActiveX XHR
10151		// is used. In IE 9+ always use the native XHR.
10152		// Note: this condition won't catch Edge as it doesn't define
10153		// document.documentMode but it also doesn't support ActiveX so it won't
10154		// reach this code.
10155		if ( document.documentMode > 8 ) {
10156			return createStandardXHR();
10157		}
10158
10159		// Support: IE<9
10160		// oldIE XHR does not support non-RFC2616 methods (#13240)
10161		// See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx
10162		// and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9
10163		// Although this check for six methods instead of eight
10164		// since IE also does not support "trace" and "connect"
10165		return /^(get|post|head|put|delete|options)$/i.test( this.type ) &&
10166			createStandardXHR() || createActiveXHR();
10167	} :
10168
10169	// For all other browsers, use the standard XMLHttpRequest object
10170	createStandardXHR;
10171
10172var xhrId = 0,
10173	xhrCallbacks = {},
10174	xhrSupported = jQuery.ajaxSettings.xhr();
10175
10176// Support: IE<10
10177// Open requests must be manually aborted on unload (#5280)
10178// See https://support.microsoft.com/kb/2856746 for more info
10179if ( window.attachEvent ) {
10180	window.attachEvent( "onunload", function() {
10181		for ( var key in xhrCallbacks ) {
10182			xhrCallbacks[ key ]( undefined, true );
10183		}
10184	} );
10185}
10186
10187// Determine support properties
10188support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
10189xhrSupported = support.ajax = !!xhrSupported;
10190
10191// Create transport if the browser can provide an xhr
10192if ( xhrSupported ) {
10193
10194	jQuery.ajaxTransport( function( options ) {
10195
10196		// Cross domain only allowed if supported through XMLHttpRequest
10197		if ( !options.crossDomain || support.cors ) {
10198
10199			var callback;
10200
10201			return {
10202				send: function( headers, complete ) {
10203					var i,
10204						xhr = options.xhr(),
10205						id = ++xhrId;
10206
10207					// Open the socket
10208					xhr.open(
10209						options.type,
10210						options.url,
10211						options.async,
10212						options.username,
10213						options.password
10214					);
10215
10216					// Apply custom fields if provided
10217					if ( options.xhrFields ) {
10218						for ( i in options.xhrFields ) {
10219							xhr[ i ] = options.xhrFields[ i ];
10220						}
10221					}
10222
10223					// Override mime type if needed
10224					if ( options.mimeType && xhr.overrideMimeType ) {
10225						xhr.overrideMimeType( options.mimeType );
10226					}
10227
10228					// X-Requested-With header
10229					// For cross-domain requests, seeing as conditions for a preflight are
10230					// akin to a jigsaw puzzle, we simply never set it to be sure.
10231					// (it can always be set on a per-request basis or even using ajaxSetup)
10232					// For same-domain requests, won't change header if already provided.
10233					if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
10234						headers[ "X-Requested-With" ] = "XMLHttpRequest";
10235					}
10236
10237					// Set headers
10238					for ( i in headers ) {
10239
10240						// Support: IE<9
10241						// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
10242						// request header to a null-value.
10243						//
10244						// To keep consistent with other XHR implementations, cast the value
10245						// to string and ignore `undefined`.
10246						if ( headers[ i ] !== undefined ) {
10247							xhr.setRequestHeader( i, headers[ i ] + "" );
10248						}
10249					}
10250
10251					// Do send the request
10252					// This may raise an exception which is actually
10253					// handled in jQuery.ajax (so no try/catch here)
10254					xhr.send( ( options.hasContent && options.data ) || null );
10255
10256					// Listener
10257					callback = function( _, isAbort ) {
10258						var status, statusText, responses;
10259
10260						// Was never called and is aborted or complete
10261						if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
10262
10263							// Clean up
10264							delete xhrCallbacks[ id ];
10265							callback = undefined;
10266							xhr.onreadystatechange = jQuery.noop;
10267
10268							// Abort manually if needed
10269							if ( isAbort ) {
10270								if ( xhr.readyState !== 4 ) {
10271									xhr.abort();
10272								}
10273							} else {
10274								responses = {};
10275								status = xhr.status;
10276
10277								// Support: IE<10
10278								// Accessing binary-data responseText throws an exception
10279								// (#11426)
10280								if ( typeof xhr.responseText === "string" ) {
10281									responses.text = xhr.responseText;
10282								}
10283
10284								// Firefox throws an exception when accessing
10285								// statusText for faulty cross-domain requests
10286								try {
10287									statusText = xhr.statusText;
10288								} catch ( e ) {
10289
10290									// We normalize with Webkit giving an empty statusText
10291									statusText = "";
10292								}
10293
10294								// Filter status for non standard behaviors
10295
10296								// If the request is local and we have data: assume a success
10297								// (success with no data won't get notified, that's the best we
10298								// can do given current implementations)
10299								if ( !status && options.isLocal && !options.crossDomain ) {
10300									status = responses.text ? 200 : 404;
10301
10302								// IE - #1450: sometimes returns 1223 when it should be 204
10303								} else if ( status === 1223 ) {
10304									status = 204;
10305								}
10306							}
10307						}
10308
10309						// Call complete if needed
10310						if ( responses ) {
10311							complete( status, statusText, responses, xhr.getAllResponseHeaders() );
10312						}
10313					};
10314
10315					// Do send the request
10316					// `xhr.send` may raise an exception, but it will be
10317					// handled in jQuery.ajax (so no try/catch here)
10318					if ( !options.async ) {
10319
10320						// If we're in sync mode we fire the callback
10321						callback();
10322					} else if ( xhr.readyState === 4 ) {
10323
10324						// (IE6 & IE7) if it's in cache and has been
10325						// retrieved directly we need to fire the callback
10326						window.setTimeout( callback );
10327					} else {
10328
10329						// Register the callback, but delay it in case `xhr.send` throws
10330						// Add to the list of active xhr callbacks
10331						xhr.onreadystatechange = xhrCallbacks[ id ] = callback;
10332					}
10333				},
10334
10335				abort: function() {
10336					if ( callback ) {
10337						callback( undefined, true );
10338					}
10339				}
10340			};
10341		}
10342	} );
10343}
10344
10345// Functions to create xhrs
10346function createStandardXHR() {
10347	try {
10348		return new window.XMLHttpRequest();
10349	} catch ( e ) {}
10350}
10351
10352function createActiveXHR() {
10353	try {
10354		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
10355	} catch ( e ) {}
10356}
10357
10358
10359
10360
10361// Install script dataType
10362jQuery.ajaxSetup( {
10363	accepts: {
10364		script: "text/javascript, application/javascript, " +
10365			"application/ecmascript, application/x-ecmascript"
10366	},
10367	contents: {
10368		script: /\b(?:java|ecma)script\b/
10369	},
10370	converters: {
10371		"text script": function( text ) {
10372			jQuery.globalEval( text );
10373			return text;
10374		}
10375	}
10376} );
10377
10378// Handle cache's special case and global
10379jQuery.ajaxPrefilter( "script", function( s ) {
10380	if ( s.cache === undefined ) {
10381		s.cache = false;
10382	}
10383	if ( s.crossDomain ) {
10384		s.type = "GET";
10385		s.global = false;
10386	}
10387} );
10388
10389// Bind script tag hack transport
10390jQuery.ajaxTransport( "script", function( s ) {
10391
10392	// This transport only deals with cross domain requests
10393	if ( s.crossDomain ) {
10394
10395		var script,
10396			head = document.head || jQuery( "head" )[ 0 ] || document.documentElement;
10397
10398		return {
10399
10400			send: function( _, callback ) {
10401
10402				script = document.createElement( "script" );
10403
10404				script.async = true;
10405
10406				if ( s.scriptCharset ) {
10407					script.charset = s.scriptCharset;
10408				}
10409
10410				script.src = s.url;
10411
10412				// Attach handlers for all browsers
10413				script.onload = script.onreadystatechange = function( _, isAbort ) {
10414
10415					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
10416
10417						// Handle memory leak in IE
10418						script.onload = script.onreadystatechange = null;
10419
10420						// Remove the script
10421						if ( script.parentNode ) {
10422							script.parentNode.removeChild( script );
10423						}
10424
10425						// Dereference the script
10426						script = null;
10427
10428						// Callback if not abort
10429						if ( !isAbort ) {
10430							callback( 200, "success" );
10431						}
10432					}
10433				};
10434
10435				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
10436				// Use native DOM manipulation to avoid our domManip AJAX trickery
10437				head.insertBefore( script, head.firstChild );
10438			},
10439
10440			abort: function() {
10441				if ( script ) {
10442					script.onload( undefined, true );
10443				}
10444			}
10445		};
10446	}
10447} );
10448
10449
10450
10451
10452var oldCallbacks = [],
10453	rjsonp = /(=)\?(?=&|$)|\?\?/;
10454
10455// Default jsonp settings
10456jQuery.ajaxSetup( {
10457	jsonp: "callback",
10458	jsonpCallback: function() {
10459		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
10460		this[ callback ] = true;
10461		return callback;
10462	}
10463} );
10464
10465// Detect, normalize options and install callbacks for jsonp requests
10466jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
10467
10468	var callbackName, overwritten, responseContainer,
10469		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
10470			"url" :
10471			typeof s.data === "string" &&
10472				( s.contentType || "" )
10473					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
10474				rjsonp.test( s.data ) && "data"
10475		);
10476
10477	// Handle iff the expected data type is "jsonp" or we have a parameter to set
10478	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
10479
10480		// Get callback name, remembering preexisting value associated with it
10481		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
10482			s.jsonpCallback() :
10483			s.jsonpCallback;
10484
10485		// Insert callback into url or form data
10486		if ( jsonProp ) {
10487			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
10488		} else if ( s.jsonp !== false ) {
10489			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
10490		}
10491
10492		// Use data converter to retrieve json after script execution
10493		s.converters[ "script json" ] = function() {
10494			if ( !responseContainer ) {
10495				jQuery.error( callbackName + " was not called" );
10496			}
10497			return responseContainer[ 0 ];
10498		};
10499
10500		// force json dataType
10501		s.dataTypes[ 0 ] = "json";
10502
10503		// Install callback
10504		overwritten = window[ callbackName ];
10505		window[ callbackName ] = function() {
10506			responseContainer = arguments;
10507		};
10508
10509		// Clean-up function (fires after converters)
10510		jqXHR.always( function() {
10511
10512			// If previous value didn't exist - remove it
10513			if ( overwritten === undefined ) {
10514				jQuery( window ).removeProp( callbackName );
10515
10516			// Otherwise restore preexisting value
10517			} else {
10518				window[ callbackName ] = overwritten;
10519			}
10520
10521			// Save back as free
10522			if ( s[ callbackName ] ) {
10523
10524				// make sure that re-using the options doesn't screw things around
10525				s.jsonpCallback = originalSettings.jsonpCallback;
10526
10527				// save the callback name for future use
10528				oldCallbacks.push( callbackName );
10529			}
10530
10531			// Call if it was a function and we have a response
10532			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
10533				overwritten( responseContainer[ 0 ] );
10534			}
10535
10536			responseContainer = overwritten = undefined;
10537		} );
10538
10539		// Delegate to script
10540		return "script";
10541	}
10542} );
10543
10544
10545
10546
10547// data: string of html
10548// context (optional): If specified, the fragment will be created in this context,
10549// defaults to document
10550// keepScripts (optional): If true, will include scripts passed in the html string
10551jQuery.parseHTML = function( data, context, keepScripts ) {
10552	if ( !data || typeof data !== "string" ) {
10553		return null;
10554	}
10555	if ( typeof context === "boolean" ) {
10556		keepScripts = context;
10557		context = false;
10558	}
10559	context = context || document;
10560
10561	var parsed = rsingleTag.exec( data ),
10562		scripts = !keepScripts && [];
10563
10564	// Single tag
10565	if ( parsed ) {
10566		return [ context.createElement( parsed[ 1 ] ) ];
10567	}
10568
10569	parsed = buildFragment( [ data ], context, scripts );
10570
10571	if ( scripts && scripts.length ) {
10572		jQuery( scripts ).remove();
10573	}
10574
10575	return jQuery.merge( [], parsed.childNodes );
10576};
10577
10578
10579// Keep a copy of the old load method
10580var _load = jQuery.fn.load;
10581
10582/**
10583 * Load a url into a page
10584 */
10585jQuery.fn.load = function( url, params, callback ) {
10586	if ( typeof url !== "string" && _load ) {
10587		return _load.apply( this, arguments );
10588	}
10589
10590	var selector, type, response,
10591		self = this,
10592		off = url.indexOf( " " );
10593
10594	if ( off > -1 ) {
10595		selector = jQuery.trim( url.slice( off, url.length ) );
10596		url = url.slice( 0, off );
10597	}
10598
10599	// If it's a function
10600	if ( jQuery.isFunction( params ) ) {
10601
10602		// We assume that it's the callback
10603		callback = params;
10604		params = undefined;
10605
10606	// Otherwise, build a param string
10607	} else if ( params && typeof params === "object" ) {
10608		type = "POST";
10609	}
10610
10611	// If we have elements to modify, make the request
10612	if ( self.length > 0 ) {
10613		jQuery.ajax( {
10614			url: url,
10615
10616			// If "type" variable is undefined, then "GET" method will be used.
10617			// Make value of this field explicit since
10618			// user can override it through ajaxSetup method
10619			type: type || "GET",
10620			dataType: "html",
10621			data: params
10622		} ).done( function( responseText ) {
10623
10624			// Save response for use in complete callback
10625			response = arguments;
10626
10627			self.html( selector ?
10628
10629				// If a selector was specified, locate the right elements in a dummy div
10630				// Exclude scripts to avoid IE 'Permission Denied' errors
10631				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
10632
10633				// Otherwise use the full result
10634				responseText );
10635
10636		// If the request succeeds, this function gets "data", "status", "jqXHR"
10637		// but they are ignored because response was set above.
10638		// If it fails, this function gets "jqXHR", "status", "error"
10639		} ).always( callback && function( jqXHR, status ) {
10640			self.each( function() {
10641				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
10642			} );
10643		} );
10644	}
10645
10646	return this;
10647};
10648
10649
10650
10651
10652// Attach a bunch of functions for handling common AJAX events
10653jQuery.each( [
10654	"ajaxStart",
10655	"ajaxStop",
10656	"ajaxComplete",
10657	"ajaxError",
10658	"ajaxSuccess",
10659	"ajaxSend"
10660], function( i, type ) {
10661	jQuery.fn[ type ] = function( fn ) {
10662		return this.on( type, fn );
10663	};
10664} );
10665
10666
10667
10668
10669jQuery.expr.filters.animated = function( elem ) {
10670	return jQuery.grep( jQuery.timers, function( fn ) {
10671		return elem === fn.elem;
10672	} ).length;
10673};
10674
10675
10676
10677
10678
10679/**
10680 * Gets a window from an element
10681 */
10682function getWindow( elem ) {
10683	return jQuery.isWindow( elem ) ?
10684		elem :
10685		elem.nodeType === 9 ?
10686			elem.defaultView || elem.parentWindow :
10687			false;
10688}
10689
10690jQuery.offset = {
10691	setOffset: function( elem, options, i ) {
10692		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
10693			position = jQuery.css( elem, "position" ),
10694			curElem = jQuery( elem ),
10695			props = {};
10696
10697		// set position first, in-case top/left are set even on static elem
10698		if ( position === "static" ) {
10699			elem.style.position = "relative";
10700		}
10701
10702		curOffset = curElem.offset();
10703		curCSSTop = jQuery.css( elem, "top" );
10704		curCSSLeft = jQuery.css( elem, "left" );
10705		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
10706			jQuery.inArray( "auto", [ curCSSTop, curCSSLeft ] ) > -1;
10707
10708		// need to be able to calculate position if either top or left
10709		// is auto and position is either absolute or fixed
10710		if ( calculatePosition ) {
10711			curPosition = curElem.position();
10712			curTop = curPosition.top;
10713			curLeft = curPosition.left;
10714		} else {
10715			curTop = parseFloat( curCSSTop ) || 0;
10716			curLeft = parseFloat( curCSSLeft ) || 0;
10717		}
10718
10719		if ( jQuery.isFunction( options ) ) {
10720
10721			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
10722			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
10723		}
10724
10725		if ( options.top != null ) {
10726			props.top = ( options.top - curOffset.top ) + curTop;
10727		}
10728		if ( options.left != null ) {
10729			props.left = ( options.left - curOffset.left ) + curLeft;
10730		}
10731
10732		if ( "using" in options ) {
10733			options.using.call( elem, props );
10734		} else {
10735			curElem.css( props );
10736		}
10737	}
10738};
10739
10740jQuery.fn.extend( {
10741	offset: function( options ) {
10742		if ( arguments.length ) {
10743			return options === undefined ?
10744				this :
10745				this.each( function( i ) {
10746					jQuery.offset.setOffset( this, options, i );
10747				} );
10748		}
10749
10750		var docElem, win,
10751			box = { top: 0, left: 0 },
10752			elem = this[ 0 ],
10753			doc = elem && elem.ownerDocument;
10754
10755		if ( !doc ) {
10756			return;
10757		}
10758
10759		docElem = doc.documentElement;
10760
10761		// Make sure it's not a disconnected DOM node
10762		if ( !jQuery.contains( docElem, elem ) ) {
10763			return box;
10764		}
10765
10766		// If we don't have gBCR, just use 0,0 rather than error
10767		// BlackBerry 5, iOS 3 (original iPhone)
10768		if ( typeof elem.getBoundingClientRect !== "undefined" ) {
10769			box = elem.getBoundingClientRect();
10770		}
10771		win = getWindow( doc );
10772		return {
10773			top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
10774			left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
10775		};
10776	},
10777
10778	position: function() {
10779		if ( !this[ 0 ] ) {
10780			return;
10781		}
10782
10783		var offsetParent, offset,
10784			parentOffset = { top: 0, left: 0 },
10785			elem = this[ 0 ];
10786
10787		// Fixed elements are offset from window (parentOffset = {top:0, left: 0},
10788		// because it is its only offset parent
10789		if ( jQuery.css( elem, "position" ) === "fixed" ) {
10790
10791			// we assume that getBoundingClientRect is available when computed position is fixed
10792			offset = elem.getBoundingClientRect();
10793		} else {
10794
10795			// Get *real* offsetParent
10796			offsetParent = this.offsetParent();
10797
10798			// Get correct offsets
10799			offset = this.offset();
10800			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
10801				parentOffset = offsetParent.offset();
10802			}
10803
10804			// Add offsetParent borders
10805			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
10806			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
10807		}
10808
10809		// Subtract parent offsets and element margins
10810		// note: when an element has margin: auto the offsetLeft and marginLeft
10811		// are the same in Safari causing offset.left to incorrectly be 0
10812		return {
10813			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
10814			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
10815		};
10816	},
10817
10818	offsetParent: function() {
10819		return this.map( function() {
10820			var offsetParent = this.offsetParent;
10821
10822			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) &&
10823				jQuery.css( offsetParent, "position" ) === "static" ) ) {
10824				offsetParent = offsetParent.offsetParent;
10825			}
10826			return offsetParent || documentElement;
10827		} );
10828	}
10829} );
10830
10831// Create scrollLeft and scrollTop methods
10832jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
10833	var top = /Y/.test( prop );
10834
10835	jQuery.fn[ method ] = function( val ) {
10836		return access( this, function( elem, method, val ) {
10837			var win = getWindow( elem );
10838
10839			if ( val === undefined ) {
10840				return win ? ( prop in win ) ? win[ prop ] :
10841					win.document.documentElement[ method ] :
10842					elem[ method ];
10843			}
10844
10845			if ( win ) {
10846				win.scrollTo(
10847					!top ? val : jQuery( win ).scrollLeft(),
10848					top ? val : jQuery( win ).scrollTop()
10849				);
10850
10851			} else {
10852				elem[ method ] = val;
10853			}
10854		}, method, val, arguments.length, null );
10855	};
10856} );
10857
10858// Support: Safari<7-8+, Chrome<37-44+
10859// Add the top/left cssHooks using jQuery.fn.position
10860// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
10861// getComputedStyle returns percent when specified for top/left/bottom/right
10862// rather than make the css module depend on the offset module, we just check for it here
10863jQuery.each( [ "top", "left" ], function( i, prop ) {
10864	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
10865		function( elem, computed ) {
10866			if ( computed ) {
10867				computed = curCSS( elem, prop );
10868
10869				// if curCSS returns percentage, fallback to offset
10870				return rnumnonpx.test( computed ) ?
10871					jQuery( elem ).position()[ prop ] + "px" :
10872					computed;
10873			}
10874		}
10875	);
10876} );
10877
10878
10879// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
10880jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
10881	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
10882	function( defaultExtra, funcName ) {
10883
10884		// margin is only for outerHeight, outerWidth
10885		jQuery.fn[ funcName ] = function( margin, value ) {
10886			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
10887				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
10888
10889			return access( this, function( elem, type, value ) {
10890				var doc;
10891
10892				if ( jQuery.isWindow( elem ) ) {
10893
10894					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
10895					// isn't a whole lot we can do. See pull request at this URL for discussion:
10896					// https://github.com/jquery/jquery/pull/764
10897					return elem.document.documentElement[ "client" + name ];
10898				}
10899
10900				// Get document width or height
10901				if ( elem.nodeType === 9 ) {
10902					doc = elem.documentElement;
10903
10904					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
10905					// whichever is greatest
10906					// unfortunately, this causes bug #3838 in IE6/8 only,
10907					// but there is currently no good, small way to fix it.
10908					return Math.max(
10909						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
10910						elem.body[ "offset" + name ], doc[ "offset" + name ],
10911						doc[ "client" + name ]
10912					);
10913				}
10914
10915				return value === undefined ?
10916
10917					// Get width or height on the element, requesting but not forcing parseFloat
10918					jQuery.css( elem, type, extra ) :
10919
10920					// Set width or height on the element
10921					jQuery.style( elem, type, value, extra );
10922			}, type, chainable ? margin : undefined, chainable, null );
10923		};
10924	} );
10925} );
10926
10927
10928jQuery.fn.extend( {
10929
10930	bind: function( types, data, fn ) {
10931		return this.on( types, null, data, fn );
10932	},
10933	unbind: function( types, fn ) {
10934		return this.off( types, null, fn );
10935	},
10936
10937	delegate: function( selector, types, data, fn ) {
10938		return this.on( types, selector, data, fn );
10939	},
10940	undelegate: function( selector, types, fn ) {
10941
10942		// ( namespace ) or ( selector, types [, fn] )
10943		return arguments.length === 1 ?
10944			this.off( selector, "**" ) :
10945			this.off( types, selector || "**", fn );
10946	}
10947} );
10948
10949// The number of elements contained in the matched element set
10950jQuery.fn.size = function() {
10951	return this.length;
10952};
10953
10954jQuery.fn.andSelf = jQuery.fn.addBack;
10955
10956
10957
10958
10959// Register as a named AMD module, since jQuery can be concatenated with other
10960// files that may use define, but not via a proper concatenation script that
10961// understands anonymous AMD modules. A named AMD is safest and most robust
10962// way to register. Lowercase jquery is used because AMD module names are
10963// derived from file names, and jQuery is normally delivered in a lowercase
10964// file name. Do this after creating the global so that if an AMD module wants
10965// to call noConflict to hide this version of jQuery, it will work.
10966
10967// Note that for maximum portability, libraries that are not jQuery should
10968// declare themselves as anonymous modules, and avoid setting a global if an
10969// AMD loader is present. jQuery is a special case. For more information, see
10970// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
10971
10972if ( typeof define === "function" && define.amd ) {
10973	define( "jquery", [], function() {
10974		return jQuery;
10975	} );
10976}
10977
10978
10979
10980var
10981
10982	// Map over jQuery in case of overwrite
10983	_jQuery = window.jQuery,
10984
10985	// Map over the $ in case of overwrite
10986	_$ = window.$;
10987
10988jQuery.noConflict = function( deep ) {
10989	if ( window.$ === jQuery ) {
10990		window.$ = _$;
10991	}
10992
10993	if ( deep && window.jQuery === jQuery ) {
10994		window.jQuery = _jQuery;
10995	}
10996
10997	return jQuery;
10998};
10999
11000// Expose jQuery and $ identifiers, even in
11001// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
11002// and CommonJS for browser emulators (#13566)
11003if ( !noGlobal ) {
11004	window.jQuery = window.$ = jQuery;
11005}
11006
11007return jQuery;
11008}));
11009/* jshint node: true */
11010
11011/**
11012 * Unobtrusive scripting adapter for jQuery
11013 * https://github.com/rails/jquery-ujs
11014 *
11015 * Requires jQuery 1.8.0 or later.
11016 *
11017 * Released under the MIT license
11018 *
11019 */
11020
11021(function() {
11022  'use strict';
11023
11024  var jqueryUjsInit = function($, undefined) {
11025
11026  // Cut down on the number of issues from people inadvertently including jquery_ujs twice
11027  // by detecting and raising an error when it happens.
11028  if ( $.rails !== undefined ) {
11029    $.error('jquery-ujs has already been loaded!');
11030  }
11031
11032  // Shorthand to make it a little easier to call public rails functions from within rails.js
11033  var rails;
11034  var $document = $(document);
11035
11036  $.rails = rails = {
11037    // Link elements bound by jquery-ujs
11038    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
11039
11040    // Button elements bound by jquery-ujs
11041    buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)',
11042
11043    // Select elements bound by jquery-ujs
11044    inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
11045
11046    // Form elements bound by jquery-ujs
11047    formSubmitSelector: 'form:not([data-turbo=true])',
11048
11049    // Form input elements bound by jquery-ujs
11050    formInputClickSelector: 'form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',
11051
11052    // Form input elements disabled during form submission
11053    disableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',
11054
11055    // Form input elements re-enabled after form submission
11056    enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',
11057
11058    // Form required input elements
11059    requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])',
11060
11061    // Form file input elements
11062    fileInputSelector: 'input[name][type=file]:not([disabled])',
11063
11064    // Link onClick disable selector with possible reenable after remote submission
11065    linkDisableSelector: 'a[data-disable-with], a[data-disable]',
11066
11067    // Button onClick disable selector with possible reenable after remote submission
11068    buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]',
11069
11070    // Up-to-date Cross-Site Request Forgery token
11071    csrfToken: function() {
11072     return $('meta[name=csrf-token]').attr('content');
11073    },
11074
11075    // URL param that must contain the CSRF token
11076    csrfParam: function() {
11077     return $('meta[name=csrf-param]').attr('content');
11078    },
11079
11080    // Make sure that every Ajax request sends the CSRF token
11081    CSRFProtection: function(xhr) {
11082      var token = rails.csrfToken();
11083      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
11084    },
11085
11086    // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
11087    refreshCSRFTokens: function(){
11088      $('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken());
11089    },
11090
11091    // Triggers an event on an element and returns false if the event result is false
11092    fire: function(obj, name, data) {
11093      var event = $.Event(name);
11094      obj.trigger(event, data);
11095      return event.result !== false;
11096    },
11097
11098    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
11099    confirm: function(message) {
11100      return confirm(message);
11101    },
11102
11103    // Default ajax function, may be overridden with custom function in $.rails.ajax
11104    ajax: function(options) {
11105      return $.ajax(options);
11106    },
11107
11108    // Default way to get an element's href. May be overridden at $.rails.href.
11109    href: function(element) {
11110      return element[0].href;
11111    },
11112
11113    // Checks "data-remote" if true to handle the request through a XHR request.
11114    isRemote: function(element) {
11115      return element.data('remote') !== undefined && element.data('remote') !== false;
11116    },
11117
11118    // Submits "remote" forms and links with ajax
11119    handleRemote: function(element) {
11120      var method, url, data, withCredentials, dataType, options;
11121
11122      if (rails.fire(element, 'ajax:before')) {
11123        withCredentials = element.data('with-credentials') || null;
11124        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
11125
11126        if (element.is('form')) {
11127          method = element.data('ujs:submit-button-formmethod') || element.attr('method');
11128          url = element.data('ujs:submit-button-formaction') || element.attr('action');
11129          data = $(element[0]).serializeArray();
11130          // memoized value from clicked submit button
11131          var button = element.data('ujs:submit-button');
11132          if (button) {
11133            data.push(button);
11134            element.data('ujs:submit-button', null);
11135          }
11136          element.data('ujs:submit-button-formmethod', null);
11137          element.data('ujs:submit-button-formaction', null);
11138        } else if (element.is(rails.inputChangeSelector)) {
11139          method = element.data('method');
11140          url = element.data('url');
11141          data = element.serialize();
11142          if (element.data('params')) data = data + '&' + element.data('params');
11143        } else if (element.is(rails.buttonClickSelector)) {
11144          method = element.data('method') || 'get';
11145          url = element.data('url');
11146          data = element.serialize();
11147          if (element.data('params')) data = data + '&' + element.data('params');
11148        } else {
11149          method = element.data('method');
11150          url = rails.href(element);
11151          data = element.data('params') || null;
11152        }
11153
11154        options = {
11155          type: method || 'GET', data: data, dataType: dataType,
11156          // stopping the "ajax:beforeSend" event will cancel the ajax request
11157          beforeSend: function(xhr, settings) {
11158            if (settings.dataType === undefined) {
11159              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
11160            }
11161            if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
11162              element.trigger('ajax:send', xhr);
11163            } else {
11164              return false;
11165            }
11166          },
11167          success: function(data, status, xhr) {
11168            element.trigger('ajax:success', [data, status, xhr]);
11169          },
11170          complete: function(xhr, status) {
11171            element.trigger('ajax:complete', [xhr, status]);
11172          },
11173          error: function(xhr, status, error) {
11174            element.trigger('ajax:error', [xhr, status, error]);
11175          },
11176          crossDomain: rails.isCrossDomain(url)
11177        };
11178
11179        // There is no withCredentials for IE6-8 when
11180        // "Enable native XMLHTTP support" is disabled
11181        if (withCredentials) {
11182          options.xhrFields = {
11183            withCredentials: withCredentials
11184          };
11185        }
11186
11187        // Only pass url to `ajax` options if not blank
11188        if (url) { options.url = url; }
11189
11190        return rails.ajax(options);
11191      } else {
11192        return false;
11193      }
11194    },
11195
11196    // Determines if the request is a cross domain request.
11197    isCrossDomain: function(url) {
11198      var originAnchor = document.createElement('a');
11199      originAnchor.href = location.href;
11200      var urlAnchor = document.createElement('a');
11201
11202      try {
11203        urlAnchor.href = url;
11204        // This is a workaround to a IE bug.
11205        urlAnchor.href = urlAnchor.href;
11206
11207        // If URL protocol is false or is a string containing a single colon
11208        // *and* host are false, assume it is not a cross-domain request
11209        // (should only be the case for IE7 and IE compatibility mode).
11210        // Otherwise, evaluate protocol and host of the URL against the origin
11211        // protocol and host.
11212        return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) ||
11213          (originAnchor.protocol + '//' + originAnchor.host ===
11214            urlAnchor.protocol + '//' + urlAnchor.host));
11215      } catch (e) {
11216        // If there is an error parsing the URL, assume it is crossDomain.
11217        return true;
11218      }
11219    },
11220
11221    // Handles "data-method" on links such as:
11222    // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
11223    handleMethod: function(link) {
11224      var href = rails.href(link),
11225        method = link.data('method'),
11226        target = link.attr('target'),
11227        csrfToken = rails.csrfToken(),
11228        csrfParam = rails.csrfParam(),
11229        form = $('<form method="post" action="' + href + '"></form>'),
11230        metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
11231
11232      if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
11233        metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
11234      }
11235
11236      if (target) { form.attr('target', target); }
11237
11238      form.hide().append(metadataInput).appendTo('body');
11239      form.submit();
11240    },
11241
11242    // Helper function that returns form elements that match the specified CSS selector
11243    // If form is actually a "form" element this will return associated elements outside the from that have
11244    // the html form attribute set
11245    formElements: function(form, selector) {
11246      return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector);
11247    },
11248
11249    /* Disables form elements:
11250      - Caches element value in 'ujs:enable-with' data store
11251      - Replaces element text with value of 'data-disable-with' attribute
11252      - Sets disabled property to true
11253    */
11254    disableFormElements: function(form) {
11255      rails.formElements(form, rails.disableSelector).each(function() {
11256        rails.disableFormElement($(this));
11257      });
11258    },
11259
11260    disableFormElement: function(element) {
11261      var method, replacement;
11262
11263      method = element.is('button') ? 'html' : 'val';
11264      replacement = element.data('disable-with');
11265
11266      if (replacement !== undefined) {
11267        element.data('ujs:enable-with', element[method]());
11268        element[method](replacement);
11269      }
11270
11271      element.prop('disabled', true);
11272      element.data('ujs:disabled', true);
11273    },
11274
11275    /* Re-enables disabled form elements:
11276      - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
11277      - Sets disabled property to false
11278    */
11279    enableFormElements: function(form) {
11280      rails.formElements(form, rails.enableSelector).each(function() {
11281        rails.enableFormElement($(this));
11282      });
11283    },
11284
11285    enableFormElement: function(element) {
11286      var method = element.is('button') ? 'html' : 'val';
11287      if (element.data('ujs:enable-with') !== undefined) {
11288        element[method](element.data('ujs:enable-with'));
11289        element.removeData('ujs:enable-with'); // clean up cache
11290      }
11291      element.prop('disabled', false);
11292      element.removeData('ujs:disabled');
11293    },
11294
11295   /* For 'data-confirm' attribute:
11296      - Fires `confirm` event
11297      - Shows the confirmation dialog
11298      - Fires the `confirm:complete` event
11299
11300      Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
11301      Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
11302      Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
11303      return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
11304   */
11305    allowAction: function(element) {
11306      var message = element.data('confirm'),
11307          answer = false, callback;
11308      if (!message) { return true; }
11309
11310      if (rails.fire(element, 'confirm')) {
11311        try {
11312          answer = rails.confirm(message);
11313        } catch (e) {
11314          (console.error || console.log).call(console, e.stack || e);
11315        }
11316        callback = rails.fire(element, 'confirm:complete', [answer]);
11317      }
11318      return answer && callback;
11319    },
11320
11321    // Helper function which checks for blank inputs in a form that match the specified CSS selector
11322    blankInputs: function(form, specifiedSelector, nonBlank) {
11323      var foundInputs = $(),
11324        input,
11325        valueToCheck,
11326        radiosForNameWithNoneSelected,
11327        radioName,
11328        selector = specifiedSelector || 'input,textarea',
11329        requiredInputs = form.find(selector),
11330        checkedRadioButtonNames = {};
11331
11332      requiredInputs.each(function() {
11333        input = $(this);
11334        if (input.is('input[type=radio]')) {
11335
11336          // Don't count unchecked required radio as blank if other radio with same name is checked,
11337          // regardless of whether same-name radio input has required attribute or not. The spec
11338          // states https://www.w3.org/TR/html5/forms.html#the-required-attribute
11339          radioName = input.attr('name');
11340
11341          // Skip if we've already seen the radio with this name.
11342          if (!checkedRadioButtonNames[radioName]) {
11343
11344            // If none checked
11345            if (form.find('input[type=radio]:checked[name="' + radioName + '"]').length === 0) {
11346              radiosForNameWithNoneSelected = form.find(
11347                'input[type=radio][name="' + radioName + '"]');
11348              foundInputs = foundInputs.add(radiosForNameWithNoneSelected);
11349            }
11350
11351            // We only need to check each name once.
11352            checkedRadioButtonNames[radioName] = radioName;
11353          }
11354        } else {
11355          valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val();
11356          if (valueToCheck === nonBlank) {
11357            foundInputs = foundInputs.add(input);
11358          }
11359        }
11360      });
11361      return foundInputs.length ? foundInputs : false;
11362    },
11363
11364    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
11365    nonBlankInputs: function(form, specifiedSelector) {
11366      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
11367    },
11368
11369    // Helper function, needed to provide consistent behavior in IE
11370    stopEverything: function(e) {
11371      $(e.target).trigger('ujs:everythingStopped');
11372      e.stopImmediatePropagation();
11373      return false;
11374    },
11375
11376    //  Replace element's html with the 'data-disable-with' after storing original html
11377    //  and prevent clicking on it
11378    disableElement: function(element) {
11379      var replacement = element.data('disable-with');
11380
11381      if (replacement !== undefined) {
11382        element.data('ujs:enable-with', element.html()); // store enabled state
11383        element.html(replacement);
11384      }
11385
11386      element.on('click.railsDisable', function(e) { // prevent further clicking
11387        return rails.stopEverything(e);
11388      });
11389      element.data('ujs:disabled', true);
11390    },
11391
11392    // Restore element to its original state which was disabled by 'disableElement' above
11393    enableElement: function(element) {
11394      if (element.data('ujs:enable-with') !== undefined) {
11395        element.html(element.data('ujs:enable-with')); // set to old enabled state
11396        element.removeData('ujs:enable-with'); // clean up cache
11397      }
11398      element.off('click.railsDisable'); // enable element
11399      element.removeData('ujs:disabled');
11400    }
11401  };
11402
11403  if (rails.fire($document, 'rails:attachBindings')) {
11404
11405    $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
11406
11407    // This event works the same as the load event, except that it fires every
11408    // time the page is loaded.
11409    //
11410    // See https://github.com/rails/jquery-ujs/issues/357
11411    // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
11412    $(window).on('pageshow.rails', function () {
11413      $($.rails.enableSelector).each(function () {
11414        var element = $(this);
11415
11416        if (element.data('ujs:disabled')) {
11417          $.rails.enableFormElement(element);
11418        }
11419      });
11420
11421      $($.rails.linkDisableSelector).each(function () {
11422        var element = $(this);
11423
11424        if (element.data('ujs:disabled')) {
11425          $.rails.enableElement(element);
11426        }
11427      });
11428    });
11429
11430    $document.on('ajax:complete', rails.linkDisableSelector, function() {
11431        rails.enableElement($(this));
11432    });
11433
11434    $document.on('ajax:complete', rails.buttonDisableSelector, function() {
11435        rails.enableFormElement($(this));
11436    });
11437
11438    $document.on('click.rails', rails.linkClickSelector, function(e) {
11439      var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
11440      if (!rails.allowAction(link)) return rails.stopEverything(e);
11441
11442      if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
11443
11444      if (rails.isRemote(link)) {
11445        if (metaClick && (!method || method === 'GET') && !data) { return true; }
11446
11447        var handleRemote = rails.handleRemote(link);
11448        // Response from rails.handleRemote() will either be false or a deferred object promise.
11449        if (handleRemote === false) {
11450          rails.enableElement(link);
11451        } else {
11452          handleRemote.fail( function() { rails.enableElement(link); } );
11453        }
11454        return false;
11455
11456      } else if (method) {
11457        rails.handleMethod(link);
11458        return false;
11459      }
11460    });
11461
11462    $document.on('click.rails', rails.buttonClickSelector, function(e) {
11463      var button = $(this);
11464
11465      if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e);
11466
11467      if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button);
11468
11469      var handleRemote = rails.handleRemote(button);
11470      // Response from rails.handleRemote() will either be false or a deferred object promise.
11471      if (handleRemote === false) {
11472        rails.enableFormElement(button);
11473      } else {
11474        handleRemote.fail( function() { rails.enableFormElement(button); } );
11475      }
11476      return false;
11477    });
11478
11479    $document.on('change.rails', rails.inputChangeSelector, function(e) {
11480      var link = $(this);
11481      if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e);
11482
11483      rails.handleRemote(link);
11484      return false;
11485    });
11486
11487    $document.on('submit.rails', rails.formSubmitSelector, function(e) {
11488      var form = $(this),
11489        remote = rails.isRemote(form),
11490        blankRequiredInputs,
11491        nonBlankFileInputs;
11492
11493      if (!rails.allowAction(form)) return rails.stopEverything(e);
11494
11495      // Skip other logic when required values are missing or file upload is present
11496      if (form.attr('novalidate') === undefined) {
11497        if (form.data('ujs:formnovalidate-button') === undefined) {
11498          blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false);
11499          if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
11500            return rails.stopEverything(e);
11501          }
11502        } else {
11503          // Clear the formnovalidate in case the next button click is not on a formnovalidate button
11504          // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain
11505          form.data('ujs:formnovalidate-button', undefined);
11506        }
11507      }
11508
11509      if (remote) {
11510        nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
11511        if (nonBlankFileInputs) {
11512          // Slight timeout so that the submit button gets properly serialized
11513          // (make it easy for event handler to serialize form without disabled values)
11514          setTimeout(function(){ rails.disableFormElements(form); }, 13);
11515          var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
11516
11517          // Re-enable form elements if event bindings return false (canceling normal form submission)
11518          if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
11519
11520          return aborted;
11521        }
11522
11523        rails.handleRemote(form);
11524        return false;
11525
11526      } else {
11527        // Slight timeout so that the submit button gets properly serialized
11528        setTimeout(function(){ rails.disableFormElements(form); }, 13);
11529      }
11530    });
11531
11532    $document.on('click.rails', rails.formInputClickSelector, function(event) {
11533      var button = $(this);
11534
11535      if (!rails.allowAction(button)) return rails.stopEverything(event);
11536
11537      // Register the pressed submit button
11538      var name = button.attr('name'),
11539        data = name ? {name:name, value:button.val()} : null;
11540
11541      var form = button.closest('form');
11542      if (form.length === 0) {
11543        form = $('#' + button.attr('form'));
11544      }
11545      form.data('ujs:submit-button', data);
11546
11547      // Save attributes from button
11548      form.data('ujs:formnovalidate-button', button.attr('formnovalidate'));
11549      form.data('ujs:submit-button-formaction', button.attr('formaction'));
11550      form.data('ujs:submit-button-formmethod', button.attr('formmethod'));
11551    });
11552
11553    $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) {
11554      if (this === event.target) rails.disableFormElements($(this));
11555    });
11556
11557    $document.on('ajax:complete.rails', rails.formSubmitSelector, function(event) {
11558      if (this === event.target) rails.enableFormElements($(this));
11559    });
11560
11561    $(function(){
11562      rails.refreshCSRFTokens();
11563    });
11564  }
11565
11566  };
11567
11568  if (window.jQuery) {
11569    jqueryUjsInit(jQuery);
11570  } else if (typeof exports === 'object' && typeof module === 'object') {
11571    module.exports = jqueryUjsInit;
11572  }
11573})();
11574"use strict";
11575
11576function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
11577
11578function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
11579
11580function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
11581
11582function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
11583
11584function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
11585
11586function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
11587
11588function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
11589
11590function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
11591
11592function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
11593
11594/*
11595 *
11596 * More info at [www.dropzonejs.com](http://www.dropzonejs.com)
11597 *
11598 * Copyright (c) 2012, Matias Meno
11599 *
11600 * Permission is hereby granted, free of charge, to any person obtaining a copy
11601 * of this software and associated documentation files (the "Software"), to deal
11602 * in the Software without restriction, including without limitation the rights
11603 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11604 * copies of the Software, and to permit persons to whom the Software is
11605 * furnished to do so, subject to the following conditions:
11606 *
11607 * The above copyright notice and this permission notice shall be included in
11608 * all copies or substantial portions of the Software.
11609 *
11610 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11611 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11612 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
11613 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
11614 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
11615 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
11616 * THE SOFTWARE.
11617 *
11618 */
11619// The Emitter class provides the ability to call `.on()` on Dropzone to listen
11620// to events.
11621// It is strongly based on component's emitter class, and I removed the
11622// functionality because of the dependency hell with different frameworks.
11623var Emitter =
11624/*#__PURE__*/
11625function () {
11626  function Emitter() {
11627    _classCallCheck(this, Emitter);
11628  }
11629
11630  _createClass(Emitter, [{
11631    key: "on",
11632    // Add an event listener for given event
11633    value: function on(event, fn) {
11634      this._callbacks = this._callbacks || {}; // Create namespace for this event
11635
11636      if (!this._callbacks[event]) {
11637        this._callbacks[event] = [];
11638      }
11639
11640      this._callbacks[event].push(fn);
11641
11642      return this;
11643    }
11644  }, {
11645    key: "emit",
11646    value: function emit(event) {
11647      this._callbacks = this._callbacks || {};
11648      var callbacks = this._callbacks[event];
11649
11650      if (callbacks) {
11651        for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
11652          args[_key - 1] = arguments[_key];
11653        }
11654
11655        var _iteratorNormalCompletion = true;
11656        var _didIteratorError = false;
11657        var _iteratorError = undefined;
11658
11659        try {
11660          for (var _iterator = callbacks[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
11661            var callback = _step.value;
11662            callback.apply(this, args);
11663          }
11664        } catch (err) {
11665          _didIteratorError = true;
11666          _iteratorError = err;
11667        } finally {
11668          try {
11669            if (!_iteratorNormalCompletion && _iterator["return"] != null) {
11670              _iterator["return"]();
11671            }
11672          } finally {
11673            if (_didIteratorError) {
11674              throw _iteratorError;
11675            }
11676          }
11677        }
11678      }
11679
11680      return this;
11681    } // Remove event listener for given event. If fn is not provided, all event
11682    // listeners for that event will be removed. If neither is provided, all
11683    // event listeners will be removed.
11684
11685  }, {
11686    key: "off",
11687    value: function off(event, fn) {
11688      if (!this._callbacks || arguments.length === 0) {
11689        this._callbacks = {};
11690        return this;
11691      } // specific event
11692
11693
11694      var callbacks = this._callbacks[event];
11695
11696      if (!callbacks) {
11697        return this;
11698      } // remove all handlers
11699
11700
11701      if (arguments.length === 1) {
11702        delete this._callbacks[event];
11703        return this;
11704      } // remove specific handler
11705
11706
11707      for (var i = 0; i < callbacks.length; i++) {
11708        var callback = callbacks[i];
11709
11710        if (callback === fn) {
11711          callbacks.splice(i, 1);
11712          break;
11713        }
11714      }
11715
11716      return this;
11717    }
11718  }]);
11719
11720  return Emitter;
11721}();
11722
11723var Dropzone =
11724/*#__PURE__*/
11725function (_Emitter) {
11726  _inherits(Dropzone, _Emitter);
11727
11728  _createClass(Dropzone, null, [{
11729    key: "initClass",
11730    value: function initClass() {
11731      // Exposing the emitter class, mainly for tests
11732      this.prototype.Emitter = Emitter;
11733      /*
11734       This is a list of all available events you can register on a dropzone object.
11735        You can register an event handler like this:
11736        dropzone.on("dragEnter", function() { });
11737        */
11738
11739      this.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete"];
11740      this.prototype.defaultOptions = {
11741        /**
11742         * Has to be specified on elements other than form (or when the form
11743         * doesn't have an `action` attribute). You can also
11744         * provide a function that will be called with `files` and
11745         * must return the url (since `v3.12.0`)
11746         */
11747        url: null,
11748
11749        /**
11750         * Can be changed to `"put"` if necessary. You can also provide a function
11751         * that will be called with `files` and must return the method (since `v3.12.0`).
11752         */
11753        method: "post",
11754
11755        /**
11756         * Will be set on the XHRequest.
11757         */
11758        withCredentials: false,
11759
11760        /**
11761         * The timeout for the XHR requests in milliseconds (since `v4.4.0`).
11762         */
11763        timeout: 30000,
11764
11765        /**
11766         * How many file uploads to process in parallel (See the
11767         * Enqueuing file uploads documentation section for more info)
11768         */
11769        parallelUploads: 2,
11770
11771        /**
11772         * Whether to send multiple files in one request. If
11773         * this it set to true, then the fallback file input element will
11774         * have the `multiple` attribute as well. This option will
11775         * also trigger additional events (like `processingmultiple`). See the events
11776         * documentation section for more information.
11777         */
11778        uploadMultiple: false,
11779
11780        /**
11781         * Whether you want files to be uploaded in chunks to your server. This can't be
11782         * used in combination with `uploadMultiple`.
11783         *
11784         * See [chunksUploaded](#config-chunksUploaded) for the callback to finalise an upload.
11785         */
11786        chunking: false,
11787
11788        /**
11789         * If `chunking` is enabled, this defines whether **every** file should be chunked,
11790         * even if the file size is below chunkSize. This means, that the additional chunk
11791         * form data will be submitted and the `chunksUploaded` callback will be invoked.
11792         */
11793        forceChunking: false,
11794
11795        /**
11796         * If `chunking` is `true`, then this defines the chunk size in bytes.
11797         */
11798        chunkSize: 2000000,
11799
11800        /**
11801         * If `true`, the individual chunks of a file are being uploaded simultaneously.
11802         */
11803        parallelChunkUploads: false,
11804
11805        /**
11806         * Whether a chunk should be retried if it fails.
11807         */
11808        retryChunks: false,
11809
11810        /**
11811         * If `retryChunks` is true, how many times should it be retried.
11812         */
11813        retryChunksLimit: 3,
11814
11815        /**
11816         * If not `null` defines how many files this Dropzone handles. If it exceeds,
11817         * the event `maxfilesexceeded` will be called. The dropzone element gets the
11818         * class `dz-max-files-reached` accordingly so you can provide visual feedback.
11819         */
11820        maxFilesize: 256,
11821
11822        /**
11823         * The name of the file param that gets transferred.
11824         * **NOTE**: If you have the option  `uploadMultiple` set to `true`, then
11825         * Dropzone will append `[]` to the name.
11826         */
11827        paramName: "file",
11828
11829        /**
11830         * Whether thumbnails for images should be generated
11831         */
11832        createImageThumbnails: true,
11833
11834        /**
11835         * In MB. When the filename exceeds this limit, the thumbnail will not be generated.
11836         */
11837        maxThumbnailFilesize: 10,
11838
11839        /**
11840         * If `null`, the ratio of the image will be used to calculate it.
11841         */
11842        thumbnailWidth: 120,
11843
11844        /**
11845         * The same as `thumbnailWidth`. If both are null, images will not be resized.
11846         */
11847        thumbnailHeight: 120,
11848
11849        /**
11850         * How the images should be scaled down in case both, `thumbnailWidth` and `thumbnailHeight` are provided.
11851         * Can be either `contain` or `crop`.
11852         */
11853        thumbnailMethod: 'crop',
11854
11855        /**
11856         * If set, images will be resized to these dimensions before being **uploaded**.
11857         * If only one, `resizeWidth` **or** `resizeHeight` is provided, the original aspect
11858         * ratio of the file will be preserved.
11859         *
11860         * The `options.transformFile` function uses these options, so if the `transformFile` function
11861         * is overridden, these options don't do anything.
11862         */
11863        resizeWidth: null,
11864
11865        /**
11866         * See `resizeWidth`.
11867         */
11868        resizeHeight: null,
11869
11870        /**
11871         * The mime type of the resized image (before it gets uploaded to the server).
11872         * If `null` the original mime type will be used. To force jpeg, for example, use `image/jpeg`.
11873         * See `resizeWidth` for more information.
11874         */
11875        resizeMimeType: null,
11876
11877        /**
11878         * The quality of the resized images. See `resizeWidth`.
11879         */
11880        resizeQuality: 0.8,
11881
11882        /**
11883         * How the images should be scaled down in case both, `resizeWidth` and `resizeHeight` are provided.
11884         * Can be either `contain` or `crop`.
11885         */
11886        resizeMethod: 'contain',
11887
11888        /**
11889         * The base that is used to calculate the filesize. You can change this to
11890         * 1024 if you would rather display kibibytes, mebibytes, etc...
11891         * 1024 is technically incorrect, because `1024 bytes` are `1 kibibyte` not `1 kilobyte`.
11892         * You can change this to `1024` if you don't care about validity.
11893         */
11894        filesizeBase: 1000,
11895
11896        /**
11897         * Can be used to limit the maximum number of files that will be handled by this Dropzone
11898         */
11899        maxFiles: null,
11900
11901        /**
11902         * An optional object to send additional headers to the server. Eg:
11903         * `{ "My-Awesome-Header": "header value" }`
11904         */
11905        headers: null,
11906
11907        /**
11908         * If `true`, the dropzone element itself will be clickable, if `false`
11909         * nothing will be clickable.
11910         *
11911         * You can also pass an HTML element, a CSS selector (for multiple elements)
11912         * or an array of those. In that case, all of those elements will trigger an
11913         * upload when clicked.
11914         */
11915        clickable: true,
11916
11917        /**
11918         * Whether hidden files in directories should be ignored.
11919         */
11920        ignoreHiddenFiles: true,
11921
11922        /**
11923         * The default implementation of `accept` checks the file's mime type or
11924         * extension against this list. This is a comma separated list of mime
11925         * types or file extensions.
11926         *
11927         * Eg.: `image/*,application/pdf,.psd`
11928         *
11929         * If the Dropzone is `clickable` this option will also be used as
11930         * [`accept`](https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept)
11931         * parameter on the hidden file input as well.
11932         */
11933        acceptedFiles: null,
11934
11935        /**
11936         * **Deprecated!**
11937         * Use acceptedFiles instead.
11938         */
11939        acceptedMimeTypes: null,
11940
11941        /**
11942         * If false, files will be added to the queue but the queue will not be
11943         * processed automatically.
11944         * This can be useful if you need some additional user input before sending
11945         * files (or if you want want all files sent at once).
11946         * If you're ready to send the file simply call `myDropzone.processQueue()`.
11947         *
11948         * See the [enqueuing file uploads](#enqueuing-file-uploads) documentation
11949         * section for more information.
11950         */
11951        autoProcessQueue: true,
11952
11953        /**
11954         * If false, files added to the dropzone will not be queued by default.
11955         * You'll have to call `enqueueFile(file)` manually.
11956         */
11957        autoQueue: true,
11958
11959        /**
11960         * If `true`, this will add a link to every file preview to remove or cancel (if
11961         * already uploading) the file. The `dictCancelUpload`, `dictCancelUploadConfirmation`
11962         * and `dictRemoveFile` options are used for the wording.
11963         */
11964        addRemoveLinks: false,
11965
11966        /**
11967         * Defines where to display the file previews – if `null` the
11968         * Dropzone element itself is used. Can be a plain `HTMLElement` or a CSS
11969         * selector. The element should have the `dropzone-previews` class so
11970         * the previews are displayed properly.
11971         */
11972        previewsContainer: null,
11973
11974        /**
11975         * This is the element the hidden input field (which is used when clicking on the
11976         * dropzone to trigger file selection) will be appended to. This might
11977         * be important in case you use frameworks to switch the content of your page.
11978         *
11979         * Can be a selector string, or an element directly.
11980         */
11981        hiddenInputContainer: "body",
11982
11983        /**
11984         * If null, no capture type will be specified
11985         * If camera, mobile devices will skip the file selection and choose camera
11986         * If microphone, mobile devices will skip the file selection and choose the microphone
11987         * If camcorder, mobile devices will skip the file selection and choose the camera in video mode
11988         * On apple devices multiple must be set to false.  AcceptedFiles may need to
11989         * be set to an appropriate mime type (e.g. "image/*", "audio/*", or "video/*").
11990         */
11991        capture: null,
11992
11993        /**
11994         * **Deprecated**. Use `renameFile` instead.
11995         */
11996        renameFilename: null,
11997
11998        /**
11999         * A function that is invoked before the file is uploaded to the server and renames the file.
12000         * This function gets the `File` as argument and can use the `file.name`. The actual name of the
12001         * file that gets used during the upload can be accessed through `file.upload.filename`.
12002         */
12003        renameFile: null,
12004
12005        /**
12006         * If `true` the fallback will be forced. This is very useful to test your server
12007         * implementations first and make sure that everything works as
12008         * expected without dropzone if you experience problems, and to test
12009         * how your fallbacks will look.
12010         */
12011        forceFallback: false,
12012
12013        /**
12014         * The text used before any files are dropped.
12015         */
12016        dictDefaultMessage: "Drop files here to upload",
12017
12018        /**
12019         * The text that replaces the default message text it the browser is not supported.
12020         */
12021        dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.",
12022
12023        /**
12024         * The text that will be added before the fallback form.
12025         * If you provide a  fallback element yourself, or if this option is `null` this will
12026         * be ignored.
12027         */
12028        dictFallbackText: "Please use the fallback form below to upload your files like in the olde
vendor: 4,628 bytes, lines 12028-12152
12028n days.",
12029
12030        /**
12031         * If the filesize is too big.
12032         * `{{filesize}}` and `{{maxFilesize}}` will be replaced with the respective configuration values.
12033         */
12034        dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",
12035
12036        /**
12037         * If the file doesn't match the file type.
12038         */
12039        dictInvalidFileType: "You can't upload files of this type.",
12040
12041        /**
12042         * If the server response was invalid.
12043         * `{{statusCode}}` will be replaced with the servers status code.
12044         */
12045        dictResponseError: "Server responded with {{statusCode}} code.",
12046
12047        /**
12048         * If `addRemoveLinks` is true, the text to be used for the cancel upload link.
12049         */
12050        dictCancelUpload: "Cancel upload",
12051
12052        /**
12053         * The text that is displayed if an upload was manually canceled
12054         */
12055        dictUploadCanceled: "Upload canceled.",
12056
12057        /**
12058         * If `addRemoveLinks` is true, the text to be used for confirmation when cancelling upload.
12059         */
12060        dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?",
12061
12062        /**
12063         * If `addRemoveLinks` is true, the text to be used to remove a file.
12064         */
12065        dictRemoveFile: "Remove file",
12066
12067        /**
12068         * If this is not null, then the user will be prompted before removing a file.
12069         */
12070        dictRemoveFileConfirmation: null,
12071
12072        /**
12073         * Displayed if `maxFiles` is st and exceeded.
12074         * The string `{{maxFiles}}` will be replaced by the configuration value.
12075         */
12076        dictMaxFilesExceeded: "You can not upload any more files.",
12077
12078        /**
12079         * Allows you to translate the different units. Starting with `tb` for terabytes and going down to
12080         * `b` for bytes.
12081         */
12082        dictFileSizeUnits: {
12083          tb: "TB",
12084          gb: "GB",
12085          mb: "MB",
12086          kb: "KB",
12087          b: "b"
12088        },
12089
12090        /**
12091         * Called when dropzone initialized
12092         * You can add event listeners here
12093         */
12094        init: function init() {},
12095
12096        /**
12097         * Can be an **object** of additional parameters to transfer to the server, **or** a `Function`
12098         * that gets invoked with the `files`, `xhr` and, if it's a chunked upload, `chunk` arguments. In case
12099         * of a function, this needs to return a map.
12100         *
12101         * The default implementation does nothing for normal uploads, but adds relevant information for
12102         * chunked uploads.
12103         *
12104         * This is the same as adding hidden input fields in the form element.
12105         */
12106        params: function params(files, xhr, chunk) {
12107          if (chunk) {
12108            return {
12109              dzuuid: chunk.file.upload.uuid,
12110              dzchunkindex: chunk.index,
12111              dztotalfilesize: chunk.file.size,
12112              dzchunksize: this.options.chunkSize,
12113              dztotalchunkcount: chunk.file.upload.totalChunkCount,
12114              dzchunkbyteoffset: chunk.index * this.options.chunkSize
12115            };
12116          }
12117        },
12118
12119        /**
12120         * A function that gets a [file](https://developer.mozilla.org/en-US/docs/DOM/File)
12121         * and a `done` function as parameters.
12122         *
12123         * If the done function is invoked without arguments, the file is "accepted" and will
12124         * be processed. If you pass an error message, the file is rejected, and the error
12125         * message will be displayed.
12126         * This function will not be called if the file is too big or doesn't match the mime types.
12127         */
12128        accept: function accept(file, done) {
12129          return done();
12130        },
12131
12132        /**
12133         * The callback that will be invoked when all chunks have been uploaded for a file.
12134         * It gets the file for which the chunks have been uploaded as the first parameter,
12135         * and the `done` function as second. `done()` needs to be invoked when everything
12136         * needed to finish the upload process is done.
12137         */
12138        chunksUploaded: function chunksUploaded(file, done) {
12139          done();
12140        },
12141
12142        /**
12143         * Gets called when the browser is not supported.
12144         * The default implementation shows the fallback input field and adds
12145         * a text.
12146         */
12147        fallback: function fallback() {
12148          // This code should pass in IE7... :(
12149          var messageElement;
12150          this.element.className = "".concat(this.element.className, " dz-browser-not-supported");
12151          var _iteratorNormalCompletion2 = true;
12152          var _didIteratorError2 = false;
12153          var _iteratorError2 = undefined;
12154
12155          try {
12156            for (var _iterator2 = this.element.getElementsByTagName("div")[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
12157              var child = _step2.value;
12158
12159              if (/(^| )dz-message($| )/.test(child.className)) {
12160                messageElement = child;
12161                child.className = "dz-message"; // Removes the 'dz-default' class
12162
12163                break;
12164              }
12165            }
12166          } catch (err) {
12167            _didIteratorError2 = true;
12168            _iteratorError2 = err;
12169          } finally {
12170            try {
12171              if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) {
12172                _iterator2["return"]();
12173              }
12174            } finally {
12175              if (_didIteratorError2) {
12176                throw _iteratorError2;
12177              }
12178            }
12179          }
12180
12181          if (!messageElement) {
12182            messageElement = Dropzone.createElement("<div class=\"dz-message\"><span></span></div>");
12183            this.element.appendChild(messageElement);
12184          }
12185
12186          var span = messageElement.getElementsByTagName("span")[0];
12187
12188          if (span) {
12189            if (span.textContent != null) {
12190              span.textContent = this.options.dictFallbackMessage;
12191            } else if (span.innerText != null) {
12192              span.innerText = this.options.dictFallbackMessage;
12193            }
12194          }
12195
12196          return this.element.appendChild(this.getFallbackForm());
12197        },
12198
12199        /**
12200         * Gets called to calculate the thumbnail dimensions.
12201         *
12202         * It gets `file`, `width` and `height` (both may be `null`) as parameters and must return an object c
vendor: 8,652 bytes, lines 12202-12344
12202ontaining:
12203         *
12204         *  - `srcWidth` & `srcHeight` (required)
12205         *  - `trgWidth` & `trgHeight` (required)
12206         *  - `srcX` & `srcY` (optional, default `0`)
12207         *  - `trgX` & `trgY` (optional, default `0`)
12208         *
12209         * Those values are going to be used by `ctx.drawImage()`.
12210         */
12211        resize: function resize(file, width, height, resizeMethod) {
12212          var info = {
12213            srcX: 0,
12214            srcY: 0,
12215            srcWidth: file.width,
12216            srcHeight: file.height
12217          };
12218          var srcRatio = file.width / file.height; // Automatically calculate dimensions if not specified
12219
12220          if (width == null && height == null) {
12221            width = info.srcWidth;
12222            height = info.srcHeight;
12223          } else if (width == null) {
12224            width = height * srcRatio;
12225          } else if (height == null) {
12226            height = width / srcRatio;
12227          } // Make sure images aren't upscaled
12228
12229
12230          width = Math.min(width, info.srcWidth);
12231          height = Math.min(height, info.srcHeight);
12232          var trgRatio = width / height;
12233
12234          if (info.srcWidth > width || info.srcHeight > height) {
12235            // Image is bigger and needs rescaling
12236            if (resizeMethod === 'crop') {
12237              if (srcRatio > trgRatio) {
12238                info.srcHeight = file.height;
12239                info.srcWidth = info.srcHeight * trgRatio;
12240              } else {
12241                info.srcWidth = file.width;
12242                info.srcHeight = info.srcWidth / trgRatio;
12243              }
12244            } else if (resizeMethod === 'contain') {
12245              // Method 'contain'
12246              if (srcRatio > trgRatio) {
12247                height = width / srcRatio;
12248              } else {
12249                width = height * srcRatio;
12250              }
12251            } else {
12252              throw new Error("Unknown resizeMethod '".concat(resizeMethod, "'"));
12253            }
12254          }
12255
12256          info.srcX = (file.width - info.srcWidth) / 2;
12257          info.srcY = (file.height - info.srcHeight) / 2;
12258          info.trgWidth = width;
12259          info.trgHeight = height;
12260          return info;
12261        },
12262
12263        /**
12264         * Can be used to transform the file (for example, resize an image if necessary).
12265         *
12266         * The default implementation uses `resizeWidth` and `resizeHeight` (if provided) and resizes
12267         * images according to those dimensions.
12268         *
12269         * Gets the `file` as the first parameter, and a `done()` function as the second, that needs
12270         * to be invoked with the file when the transformation is done.
12271         */
12272        transformFile: function transformFile(file, done) {
12273          if ((this.options.resizeWidth || this.options.resizeHeight) && file.type.match(/image.*/)) {
12274            return this.resizeImage(file, this.options.resizeWidth, this.options.resizeHeight, this.options.resizeMethod, done);
12275          } else {
12276            return done(file);
12277          }
12278        },
12279
12280        /**
12281         * A string that contains the template used for each dropped
12282         * file. Change it to fulfill your needs but make sure to properly
12283         * provide all elements.
12284         *
12285         * If you want to use an actual HTML element instead of providing a String
12286         * as a config option, you could create a div with the id `tpl`,
12287         * put the template inside it and provide the element like this:
12288         *
12289         *     document
12290         *       .querySelector('#tpl')
12291         *       .innerHTML
12292         *
12293         */
12294        previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n  <div class=\"dz-image\"><img data-dz-thumbnail /></div>\n  <div class=\"dz-details\">\n    <div class=\"dz-size\"><span data-dz-size></span></div>\n    <div class=\"dz-filename\"><span data-dz-name></span></div>\n  </div>\n  <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n  <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n  <div class=\"dz-success-mark\">\n    <svg width=\"54px\" height=\"54px\" viewBox=\"0 0 54 54\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n      <title>Check</title>\n      <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n        <path d=\"M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z\" stroke-opacity=\"0.198794158\" stroke=\"#747474\" fill-opacity=\"0.816519475\" fill=\"#FFFFFF\"></path>\n      </g>\n    </svg>\n  </div>\n  <div class=\"dz-error-mark\">\n    <svg width=\"54px\" height=\"54px\" viewBox=\"0 0 54 54\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n      <title>Error</title>\n      <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n        <g stroke=\"#747474\" stroke-opacity=\"0.198794158\" fill=\"#FFFFFF\" fill-opacity=\"0.816519475\">\n          <path d=\"M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z\"></path>\n        </g>\n      </g>\n    </svg>\n  </div>\n</div>",
12295        // END OPTIONS
12296        // (Required by the dropzone documentation parser)
12297
12298        /*
12299         Those functions register themselves to the events on init and handle all
12300         the user interface specific stuff. Overwriting them won't break the upload
12301         but can break the way it's displayed.
12302         You can overwrite them if you don't like the default behavior. If you just
12303         want to add an additional event handler, register it on the dropzone object
12304         and don't overwrite those options.
12305         */
12306        // Those are self explanatory and simply concern the DragnDrop.
12307        drop: function drop(e) {
12308          return this.element.classList.remove("dz-drag-hover");
12309        },
12310        dragstart: function dragstart(e) {},
12311        dragend: function dragend(e) {
12312          return this.element.classList.remove("dz-drag-hover");
12313        },
12314        dragenter: function dragenter(e) {
12315          return this.element.classList.add("dz-drag-hover");
12316        },
12317        dragover: function dragover(e) {
12318          return this.element.classList.add("dz-drag-hover");
12319        },
12320        dragleave: function dragleave(e) {
12321          return this.element.classList.remove("dz-drag-hover");
12322        },
12323        paste: function paste(e) {},
12324        // Called whenever there are no files left in the dropzone anymore, and the
12325        // dropzone should be displayed as if in the initial state.
12326        reset: function reset() {
12327          return this.element.classList.remove("dz-started");
12328        },
12329        // Called when a file is added to the queue
12330        // Receives `file`
12331        addedfile: function addedfile(file) {
12332          var _this2 = this;
12333
12334          if (this.element === this.previewsContainer) {
12335            this.element.classList.add("dz-started");
12336          }
12337
12338          if (this.previewsContainer) {
12339            file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim());
12340            file.previewTemplate = file.previewElement; // Backwards compatibility
12341
12342            this.previewsContainer.appendChild(file.previewElement);
12343            var _iteratorNormalCompletion3 = true;
12344            var _didIteratorError3 = false;
12345            var _iteratorError3 = undefined;
12346
12347            try {
12348              for (var _iterator3 = file.previewElement.querySelectorAll("[data-dz-name]")[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
12349                var node = _step3.value;
12350                node.textContent = file.name;
12351              }
12352            } catch (err) {
12353              _didIteratorError3 = true;
12354              _iteratorError3 = err;
12355            } finally {
12356              try {
12357                if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) {
12358                  _iterator3["return"]();
12359                }
12360              } finally {
12361                if (_didIteratorError3) {
12362                  throw _iteratorError3;
12363                }
12364              }
12365            }
12366
12367            var _iteratorNormalCompletion4 = true;
12368            var _didIteratorError4 = false;
12369            var _iteratorError4 = undefined;
12370
12371            try {
12372              for (var _iterator4 = file.previewElement.querySelectorAll("[data-dz-size]")[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
12373                node = _step4.value;
12374                node.innerHTML = this.filesize(file.size);
12375              }
12376            } catch (err) {
12377              _didIteratorError4 = true;
12378              _iteratorError4 = err;
12379            } finally {
12380              try {
12381                if (!_iteratorNormalCompletion4 && _iterator4["return"] != null) {
12382                  _iterator4["return"]();
12383                }
12384              } finally {
12385                if (_didIteratorError4) {
12386                  throw _iteratorError4;
12387                }
12388              }
12389            }
12390
12391            if (this.options.addRemoveLinks) {
12392              file._removeLink = Dropzone.createElement("<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>".concat(this.options.dictRemoveFile, "</a>"));
12393              file.previewElement.appendChild(file._removeLink);
12394            }
12395
12396            var removeFileEvent = function removeFileEvent(e) {
12397              e.preventDefault();
12398              e.stopPropagation();
12399
12400              if (file.status === Dropzone.UPLOADING) {
12401                return Dropzone.confirm(_this2.options.dictCancelUploadConfirmation, function () {
12402                  return _this2.removeFile(file);
12403                });
12404              } else {
12405                if (_this2.options.dictRemoveFileConfirmation) {
12406                  return Dropzone.confirm(_this2.options.dictRemoveFileConfirmation, function () {
12407                    return _this2.removeFile(file);
12408                  });
12409                } else {
12410                  return _this2.removeFile(file);
12411                }
12412              }
12413            };
12414
12415            var _iteratorNormalCompletion5 = true;
12416            var _didIteratorError5 = false;
12417            var _iteratorError5 = undefined;
12418
12419            try {
12420              for (var _iterator5 = file.previewElement.querySelectorAll("[data-dz-remove]")[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
12421                var removeLink = _step5.value;
12422                removeLink.addEventListener("click", removeFileEvent);
12423              }
12424            } catch (err) {
12425              _didIteratorError5 = true;
12426              _iteratorError5 = err;
12427            } finally {
12428              try {
12429                if (!_iteratorNormalCompletion5 && _iterator5["return"] != null) {
12430                  _iterator5["return"]();
12431                }
12432              } finally {
12433                if (_didIteratorError5) {
12434                  throw _iteratorError5;
12435                }
12436              }
12437            }
12438          }
12439        },
12440        // Called whenever a file is removed.
12441        removedfile: function removedfile(file) {
12442          if (file.previewElement != null && file.previewElement.parentNode != null) {
12443            file.previewElement.parentNode.removeChild(file.previewElement);
12444          }
12445
12446          return this._updateMaxFilesReachedClass();
12447        },
12448        // Called when a thumbnail has been generated
12449        // Receives `file` and `dataUrl`
12450        thumbnail: function thumbnail(file, dataUrl) {
12451          if (file.previewElement) {
12452            file.previewElement.classList.remove("dz-file-preview");
12453            var _iteratorNormalCompletion6 = true;
12454            var _didIteratorError6 = false;
12455            var _iteratorError6 = undefined;
12456
12457            try {
12458              for (var _iterator6 = file.previewElement.querySelectorAll("[data-dz-thumbnail]")[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
12459                var thumbnailElement = _step6.value;
12460                thumbnailElement.alt = file.name;
12461                thumbnailElement.src = dataUrl;
12462              }
12463            } catch (err) {
12464              _didIteratorError6 = true;
12465              _iteratorError6 = err;
12466            } finally {
12467              try {
12468                if (!_iteratorNormalCompletion6 && _iterator6["return"] != null) {
12469                  _iterator6["return"]();
12470                }
12471              } finally {
12472                if (_didIteratorError6) {
12473                  throw _iteratorError6;
12474                }
12475              }
12476            }
12477
12478            return setTimeout(function () {
12479              return file.previewElement.classList.add("dz-image-preview");
12480            }, 1);
12481          }
12482        },
12483        // Called whenever an error occurs
12484        // Receives `file` and `message`
12485        error: function error(file, message) {
12486          if (file.previewElement) {
12487            file.previewElement.classList.add("dz-error");
12488
12489            if (typeof message !== "String" && message.error) {
12490              message = message.error;
12491            }
12492
12493            var _iteratorNormalCompletion7 = true;
12494            var _didIteratorError7 = false;
12495            var _iteratorError7 = undefined;
12496
12497            try {
12498              for (var _iterator7 = file.previewElement.querySelectorAll("[data-dz-errormessage]")[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
12499                var node = _step7.value;
12500                node.textContent = message;
12501              }
12502            } catch (err) {
12503              _didIteratorError7 = true;
12504              _iteratorError7 = err;
12505            } finally {
12506              try {
12507                if (!_iteratorNormalCompletion7 && _iterator7["return"] != null) {
12508                  _iterator7["return"]();
12509                }
12510              } finally {
12511                if (_didIteratorError7) {
12512                  throw _iteratorError7;
12513                }
12514              }
12515            }
12516          }
12517        },
12518        errormultiple: function errormultiple() {},
12519        // Called when a file gets processed. Since there is a cue, not all added
12520        // files are processed immediately.
12521        // Receives `file`
12522        processing: function processing(file) {
12523          if (file.previewElement) {
12524            file.previewElement.classList.add("dz-processing");
12525
12526            if (file._removeLink) {
12527              return file._removeLink.innerHTML = this.options.dictCancelUpload;
12528            }
12529          }
12530        },
12531        processingmultiple: function processingmultiple() {},
12532        // Called whenever the upload progress gets updated.
12533        // Receives `file`, `progress` (percentage 0-100) and `bytesSent`.
12534        // To get the total number of bytes of the file, use `file.size`
12535        uploadprogress: function uploadprogress(file, progress, bytesSent) {
12536          if (file.previewElement) {
12537            var _iteratorNormalCompletion8 = true;
12538            var _didIteratorError8 = false;
12539            var _iteratorError8 = undefined;
12540
12541            try {
12542              for (var _iterator8 = file.previewElement.querySelectorAll("[data-dz-uploadprogress]")[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
12543                var node = _step8.value;
12544                node.nodeName === 'PROGRESS' ? node.value = progress : node.style.width = "".concat(progress, "%");
12545              }
12546            } catch (err) {
12547              _didIteratorError8 = true;
12548              _iteratorError8 = err;
12549            } finally {
12550              try {
12551                if (!_iteratorNormalCompletion8 && _iterator8["return"] != null) {
12552                  _iterator8["return"]();
12553                }
12554              } finally {
12555                if (_didIteratorError8) {
12556                  throw _iteratorError8;
12557                }
12558              }
12559            }
12560          }
12561        },
12562        // Called whenever the total upload progress gets updated.
12563        // Called with totalUploadProgress (0-100), totalBytes and totalBytesSent
12564        totaluploadprogress: function totaluploadprogress() {},
12565        // Called just before the file is sent. Gets the `xhr` object as second
12566        // parameter, so you can modify it (for example to add a CSRF token) and a
12567        // `formData` object to add additional information.
12568        sending: function sending() {},
12569        sendingmultiple: function sendingmultiple() {},
12570        // When the complete upload is finished and successful
12571        // Receives `file`
12572        success: function success(file) {
12573          if (file.previewElement) {
12574            return file.previewElement.classList.add("dz-success");
12575          }
12576        },
12577        successmultiple: function successmultiple() {},
12578        // When the upload is canceled.
12579        canceled: function canceled(file) {
12580          return this.emit("error", file, this.options.dictUploadCanceled);
12581        },
12582        canceledmultiple: function canceledmultiple() {},
12583        // When the upload is finished, either with success or an error.
12584        // Receives `file`
12585        complete: function complete(file) {
12586          if (file._removeLink) {
12587            file._removeLink.innerHTML = this.options.dictRemoveFile;
12588          }
12589
12590          if (file.previewElement) {
12591            return file.previewElement.classList.add("dz-complete");
12592          }
12593        },
12594        completemultiple: function completemultiple() {},
12595        maxfilesexceeded: function maxfilesexceeded() {},
12596        maxfilesreached: function maxfilesreached() {},
12597        queuecomplete: function queuecomplete() {},
12598        addedfiles: function addedfiles() {}
12599      };
12600      this.prototype._thumbnailQueue = [];
12601      this.prototype._processingThumbnail = false;
12602    } // global utility
12603
12604  }, {
12605    key: "extend",
12606    value: function extend(target) {
12607      for (var _len2 = arguments.length, objects = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
12608        objects[_key2 - 1] = arguments[_key2];
12609      }
12610
12611      for (var _i = 0, _objects = objects; _i < _objects.length; _i++) {
12612        var object = _objects[_i];
12613
12614        for (var key in object) {
12615          var val = object[key];
12616          target[key] = val;
12617        }
12618      }
12619
12620      return target;
12621    }
12622  }]);
12623
12624  function Dropzone(el, options) {
12625    var _this;
12626
12627    _classCallCheck(this, Dropzone);
12628
12629    _this = _possibleConstructorReturn(this, _getPrototypeOf(Dropzone).call(this));
12630    var fallback, left;
12631    _this.element = el; // For backwards compatibility since the version was in the prototype previously
12632
12633    _this.version = Dropzone.version;
12634    _this.defaultOptions.previewTemplate = _this.defaultOptions.previewTemplate.replace(/\n*/g, "");
12635    _this.clickableElements = [];
12636    _this.listeners = [];
12637    _this.files = []; // All files
12638
12639    if (typeof _this.element === "string") {
12640      _this.element = document.querySelector(_this.element);
12641    } // Not checking if instance of HTMLElement or Element since IE9 is extremely weird.
12642
12643
12644    if (!_this.element || _this.element.nodeType == null) {
12645      throw new Error("Invalid dropzone element.");
12646    }
12647
12648    if (_this.element.dropzone) {
12649      throw new Error("Dropzone already attached.");
12650    } // Now add this dropzone to the instances.
12651
12652
12653    Dropzone.instances.push(_assertThisInitialized(_this)); // Put the dropzone inside the element itself.
12654
12655    _this.element.dropzone = _assertThisInitialized(_this);
12656    var elementOptions = (left = Dropzone.optionsForElement(_this.element)) != null ? left : {};
12657    _this.options = Dropzone.extend({}, _this.defaultOptions, elementOptions, options != null ? options : {}); // If the browser failed, just call the fallback and leave
12658
12659    if (_this.options.forceFallback || !Dropzone.isBrowserSupported()) {
12660      return _possibleConstructorReturn(_this, _this.options.fallback.call(_assertThisInitialized(_this)));
12661    } // @options.url = @element.getAttribute "action" unless @options.url?
12662
12663
12664    if (_this.options.url == null) {
12665      _this.options.url = _this.element.getAttribute("action");
12666    }
12667
12668    if (!_this.options.url) {
12669      throw new Error("No URL provided.");
12670    }
12671
12672    if (_this.options.acceptedFiles && _this.options.acceptedMimeTypes) {
12673      throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated.");
12674    }
12675
12676    if (_this.options.uploadMultiple && _this.options.chunking) {
12677      throw new Error('You cannot set both: uploadMultiple and chunking.');
12678    } // Backwards compatibility
12679
12680
12681    if (_this.options.acceptedMimeTypes) {
12682      _this.options.acceptedFiles = _this.options.acceptedMimeTypes;
12683      delete _this.options.acceptedMimeTypes;
12684    } // Backwards compatibility
12685
12686
12687    if (_this.options.renameFilename != null) {
12688      _this.options.renameFile = function (file) {
12689        return _this.options.renameFilename.call(_assertThisInitialized(_this), file.name, file);
12690      };
12691    }
12692
12693    _this.options.method = _this.options.method.toUpperCase();
12694
12695    if ((fallback = _this.getExistingFallback()) && fallback.parentNode) {
12696      // Remove the fallback
12697      fallback.parentNode.removeChild(fallback);
12698    } // Display previews in the previewsContainer element or the Dropzone element unless explicitly set to false
12699
12700
12701    if (_this.options.previewsContainer !== false) {
12702      if (_this.options.previewsContainer) {
12703        _this.previewsContainer = Dropzone.getElement(_this.options.previewsContainer, "previewsContainer");
12704      } else {
12705        _this.previewsContainer = _this.element;
12706      }
12707    }
12708
12709    if (_this.options.clickable) {
12710      if (_this.options.clickable === true) {
12711        _this.clickableElements = [_this.element];
12712      } else {
12713        _this.clickableElements = Dropzone.getElements(_this.options.clickable, "clickable");
12714      }
12715    }
12716
12717    _this.init();
12718
12719    return _this;
12720  } // Returns all files that have been accepted
12721
12722
12723  _createClass(Dropzone, [{
12724    key: "getAcceptedFiles",
12725    value: function getAcceptedFiles() {
12726      return this.files.filter(function (file) {
12727        return file.accepted;
12728      }).map(function (file) {
12729        return file;
12730      });
12731    } // Returns all files that have been rejected
12732    // Not sure when that's going to be useful, but added for completeness.
12733
12734  }, {
12735    key: "getRejectedFiles",
12736    value: function getRejectedFiles() {
12737      return this.files.filter(function (file) {
12738        return !file.accepted;
12739      }).map(function (file) {
12740        return file;
12741      });
12742    }
12743  }, {
12744    key: "getFilesWithStatus",
12745    value: function getFilesWithStatus(status) {
12746      return this.files.filter(function (file) {
12747        return file.status === status;
12748      }).map(function (file) {
12749        return file;
12750      });
12751    } // Returns all files that are in the queue
12752
12753  }, {
12754    key: "getQueuedFiles",
12755    value: function getQueuedFiles() {
12756      return this.getFilesWithStatus(Dropzone.QUEUED);
12757    }
12758  }, {
12759    key: "getUploadingFiles",
12760    value: function getUploadingFiles() {
12761      return this.getFilesWithStatus(Dropzone.UPLOADING);
12762    }
12763  }, {
12764    key: "getAddedFiles",
12765    value: function getAddedFiles() {
12766      return this.getFilesWithStatus(Dropzone.ADDED);
12767    } // Files that are either queued or uploading
12768
12769  }, {
12770    key: "getActiveFiles",
12771    value: function getActiveFiles() {
12772      return this.files.filter(function (file) {
12773        return file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED;
12774      }).map(function (file) {
12775        return file;
12776      });
12777    } // The function that gets called when Dropzone is initialized. You
12778    // can (and should) setup event listeners inside this function.
12779
12780  }, {
12781    key: "init",
12782    value: function init() {
12783      var _this3 = this;
12784
12785      // In case it isn't set already
12786      if (this.element.tagName === "form") {
12787        this.element.setAttribute("enctype", "multipart/form-data");
12788      }
12789
12790      if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) {
12791        this.element.appendChild(Dropzone.createElement("<div class=\"dz-default dz-message\"><button class=\"dz-button\" type=\"button\">".concat(this.options.dictDefaultMessage, "</button></div>")));
12792      }
12793
12794      if (this.clickableElements.length) {
12795        var setupHiddenFileInput = function setupHiddenFileInput() {
12796          if (_this3.hiddenFileInput) {
12797            _this3.hiddenFileInput.parentNode.removeChild(_this3.hiddenFileInput);
12798          }
12799
12800          _this3.hiddenFileInput = document.createElement("input");
12801
12802          _this3.hiddenFileInput.setAttribute("type", "file");
12803
12804          if (_this3.options.maxFiles === null || _this3.options.maxFiles > 1) {
12805            _this3.hiddenFileInput.setAttribute("multiple", "multiple");
12806          }
12807
12808          _this3.hiddenFileInput.className = "dz-hidden-input";
12809
12810          if (_this3.options.acceptedFiles !== null) {
12811            _this3.hiddenFileInput.setAttribute("accept", _this3.options.acceptedFiles);
12812          }
12813
12814          if (_this3.options.capture !== null) {
12815            _this3.hiddenFileInput.setAttribute("capture", _this3.options.capture);
12816          } // Not setting `display="none"` because some browsers don't accept clicks
12817          // on elements that aren't displayed.
12818
12819
12820          _this3.hiddenFileInput.style.visibility = "hidden";
12821          _this3.hiddenFileInput.style.position = "absolute";
12822          _this3.hiddenFileInput.style.top = "0";
12823          _this3.hiddenFileInput.style.left = "0";
12824          _this3.hiddenFileInput.style.height = "0";
12825          _this3.hiddenFileInput.style.width = "0";
12826          Dropzone.getElement(_this3.options.hiddenInputContainer, 'hiddenInputContainer').appendChild(_this3.hiddenFileInput);
12827          return _this3.hiddenFileInput.addEventListener("change", function () {
12828            var files = _this3.hiddenFileInput.files;
12829
12830            if (files.length) {
12831              var _iteratorNormalCompletion9 = true;
12832              var _didIteratorError9 = false;
12833              var _iteratorError9 = undefined;
12834
12835              try {
12836                for (var _iterator9 = files[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {
12837                  var file = _step9.value;
12838
12839                  _this3.addFile(file);
12840                }
12841              } catch (err) {
12842                _didIteratorError9 = true;
12843                _iteratorError9 = err;
12844              } finally {
12845                try {
12846                  if (!_iteratorNormalCompletion9 && _iterator9["return"] != null) {
12847                    _iterator9["return"]();
12848                  }
12849                } finally {
12850                  if (_didIteratorError9) {
12851                    throw _iteratorError9;
12852                  }
12853                }
12854              }
12855            }
12856
12857            _this3.emit("addedfiles", files);
12858
12859            return setupHiddenFileInput();
12860          });
12861        };
12862
12863        setupHiddenFileInput();
12864      }
12865
12866      this.URL = window.URL !== null ? window.URL : window.webkitURL; // Setup all event listeners on the Dropzone object itself.
12867      // They're not in @setupEventListeners() because they shouldn't be removed
12868      // again when the dropzone gets disabled.
12869
12870      var _iteratorNormalCompletion10 = true;
12871      var _didIteratorError10 = false;
12872      var _iteratorError10 = undefined;
12873
12874      try {
12875        for (var _iterator10 = this.events[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) {
12876          var eventName = _step10.value;
12877          this.on(eventName, this.options[eventName]);
12878        }
12879      } catch (err) {
12880        _didIteratorError10 = true;
12881        _iteratorError10 = err;
12882      } finally {
12883        try {
12884          if (!_iteratorNormalCompletion10 && _iterator10["return"] != null) {
12885            _iterator10["return"]();
12886          }
12887        } finally {
12888          if (_didIteratorError10) {
12889            throw _iteratorError10;
12890          }
12891        }
12892      }
12893
12894      this.on("uploadprogress", function () {
12895        return _this3.updateTotalUploadProgress();
12896      });
12897      this.on("removedfile", function () {
12898        return _this3.updateTotalUploadProgress();
12899      });
12900      this.on("canceled", function (file) {
12901        return _this3.emit("complete", file);
12902      }); // Emit a `queuecomplete` event if all files finished uploading.
12903
12904      this.on("complete", function (file) {
12905        if (_this3.getAddedFiles().length === 0 && _this3.getUploadingFiles().length === 0 && _this3.getQueuedFiles().length === 0) {
12906          // This needs to be deferred so that `queuecomplete` really triggers after `complete`
12907          return setTimeout(function () {
12908            return _this3.emit("queuecomplete");
12909          }, 0);
12910        }
12911      });
12912
12913      var containsFiles = function containsFiles(e) {
12914        if (e.dataTransfer.types) {
12915          // Because e.dataTransfer.types is an Object in
12916          // IE, we need to iterate like this instead of
12917          // using e.dataTransfer.types.some()
12918          for (var i = 0; i < e.dataTransfer.types.length; i++) {
12919            if (e.dataTransfer.types[i] === "Files") return true;
12920          }
12921        }
12922
12923        return false;
12924      };
12925
12926      var noPropagation = function noPropagation(e) {
12927        // If there are no files, we don't want to stop
12928        // propagation so we don't interfere with other
12929        // drag and drop behaviour.
12930        if (!containsFiles(e)) return;
12931        e.stopPropagation();
12932
12933        if (e.preventDefault) {
12934          return e.preventDefault();
12935        } else {
12936          return e.returnValue = false;
12937        }
12938      }; // Create the listeners
12939
12940
12941      this.listeners = [{
12942        element: this.element,
12943        events: {
12944          "dragstart": function dragstart(e) {
12945            return _this3.emit("dragstart", e);
12946          },
12947          "dragenter": function dragenter(e) {
12948            noPropagation(e);
12949            return _this3.emit("dragenter", e);
12950          },
12951          "dragover": function dragover(e) {
12952            // Makes it possible to drag files from chrome's download bar
12953            // http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar
12954            // Try is required to prevent bug in Internet Explorer 11 (SCRIPT65535 exception)
12955            var efct;
12956
12957            try {
12958              efct = e.dataTransfer.effectAllowed;
12959            } catch (error) {}
12960
12961            e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy';
12962            noPropagation(e);
12963            return _this3.emit("dragover", e);
12964          },
12965          "dragleave": function dragleave(e) {
12966            return _this3.emit("dragleave", e);
12967          },
12968          "drop": function drop(e) {
12969            noPropagation(e);
12970            return _this3.drop(e);
12971          },
12972          "dragend": function dragend(e) {
12973            return _this3.emit("dragend", e);
12974          }
12975        } // This is disabled right now, because the browsers don't implement it properly.
12976        // "paste": (e) =>
12977        //   noPropagation e
12978        //   @paste e
12979
12980      }];
12981      this.clickableElements.forEach(function (clickableElement) {
12982        return _this3.listeners.push({
12983          element: clickableElement,
12984          events: {
12985            "click": function click(evt) {
12986              // Only the actual dropzone or the message element should trigger file selection
12987              if (clickableElement !== _this3.element || evt.target === _this3.element || Dropzone.elementInside(evt.target, _this3.element.querySelector(".dz-message"))) {
12988                _this3.hiddenFileInput.click(); // Forward the click
12989
12990              }
12991
12992              return true;
12993            }
12994          }
12995        });
12996      });
12997      this.enable();
12998      return this.options.init.call(this);
12999    } // Not fully tested yet
13000
13001  }, {
13002    key: "destroy",
13003    value: function destroy() {
13004      this.disable();
13005      this.removeAllFiles(true);
13006
13007      if (this.hiddenFileInput != null ? this.hiddenFileInput.parentNode : undefined) {
13008        this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput);
13009        this.hiddenFileInput = null;
13010      }
13011
13012      delete this.element.dropzone;
13013      return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1);
13014    }
13015  }, {
13016    key: "updateTotalUploadProgress",
13017    value: function updateTotalUploadProgress() {
13018      var totalUploadProgress;
13019      var totalBytesSent = 0;
13020      var totalBytes = 0;
13021      var activeFiles = this.getActiveFiles();
13022
13023      if (activeFiles.length) {
13024        var _iteratorNormalCompletion11 = true;
13025        var _didIteratorError11 = false;
13026        var _iteratorError11 = undefined;
13027
13028        try {
13029          for (var _iterator11 = this.getActiveFiles()[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) {
13030            var file = _step11.value;
13031            totalBytesSent += file.upload.bytesSent;
13032            totalBytes += file.upload.total;
13033          }
13034        } catch (err) {
13035          _didIteratorError11 = true;
13036          _iteratorError11 = err;
13037        } finally {
13038          try {
13039            if (!_iteratorNormalCompletion11 && _iterator11["return"] != null) {
13040              _iterator11["return"]();
13041            }
13042          } finally {
13043            if (_didIteratorError11) {
13044              throw _iteratorError11;
13045            }
13046          }
13047        }
13048
13049        totalUploadProgress = 100 * totalBytesSent / totalBytes;
13050      } else {
13051        totalUploadProgress = 100;
13052      }
13053
13054      return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent);
13055    } // @options.paramName can be a function taking one parameter rather than a string.
13056    // A parameter name for a file is obtained simply by calling this with an index number.
13057
13058  }, {
13059    key: "_getParamName",
13060    value: function _getParamName(n) {
13061      if (typeof this.options.paramName === "function") {
13062        return this.options.paramName(n);
13063      } else {
13064        return "".concat(this.options.paramName).concat(this.options.uploadMultiple ? "[".concat(n, "]") : "");
13065      }
13066    } // If @options.renameFile is a function,
13067    // the function will be used to rename the file.name before appending it to the formData
13068
13069  }, {
13070    key: "_renameFile",
13071    value: function _renameFile(file) {
13072      if (typeof this.options.renameFile !== "function") {
13073        return file.name;
13074      }
13075
13076      return this.options.renameFile(file);
13077    } // Returns a form that can be used as fallback if the browser does not support DragnDrop
13078    //
13079    // If the dropzone is already a form, only the input field and button are returned. Otherwise a complete form element is provided.
13080    // This code has to pass in IE7 :(
13081
13082  }, {
13083    key: "getFallbackForm",
13084    value: function getFallbackForm() {
13085      var existingFallback, form;
13086
13087      if (existingFallback = this.getExistingFallback()) {
13088        return existingFallback;
13089      }
13090
13091      var fieldsString = "<div class=\"dz-fallback\">";
13092
13093      if (this.options.dictFallbackText) {
13094        fieldsString += "<p>".concat(this.options.dictFallbackText, "</p>");
13095      }
13096
13097      fieldsString += "<input type=\"file\" name=\"".concat(this._getParamName(0), "\" ").concat(this.options.uploadMultiple ? 'multiple="multiple"' : undefined, " /><input type=\"submit\" value=\"Upload!\"></div>");
13098      var fields = Dropzone.createElement(fieldsString);
13099
13100      if (this.element.tagName !== "FORM") {
13101        form = Dropzone.createElement("<form action=\"".concat(this.options.url, "\" enctype=\"multipart/form-data\" method=\"").concat(this.options.method, "\"></form>"));
13102        form.appendChild(fields);
13103      } else {
13104        // Make sure that the enctype and method attributes are set properly
13105        this.element.setAttribute("enctype", "multipart/form-data");
13106        this.element.setAttribute("method", this.options.method);
13107      }
13108
13109      return form != null ? form : fields;
13110    } // Returns the fallback elements if they exist already
13111    //
13112    // This code has to pass in IE7 :(
13113
13114  }, {
13115    key: "getExistingFallback",
13116    value: function getExistingFallback() {
13117      var getFallback = function getFallback(elements) {
13118        var _iteratorNormalCompletion12 = true;
13119        var _didIteratorError12 = false;
13120        var _iteratorError12 = undefined;
13121
13122        try {
13123          for (var _iterator12 = elements[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) {
13124            var el = _step12.value;
13125
13126            if (/(^| )fallback($| )/.test(el.className)) {
13127              return el;
13128            }
13129          }
13130        } catch (err) {
13131          _didIteratorError12 = true;
13132          _iteratorError12 = err;
13133        } finally {
13134          try {
13135            if (!_iteratorNormalCompletion12 && _iterator12["return"] != null) {
13136              _iterator12["return"]();
13137            }
13138          } finally {
13139            if (_didIteratorError12) {
13140              throw _iteratorError12;
13141            }
13142          }
13143        }
13144      };
13145
13146      for (var _i2 = 0, _arr = ["div", "form"]; _i2 < _arr.length; _i2++) {
13147        var tagName = _arr[_i2];
13148        var fallback;
13149
13150        if (fallback = getFallback(this.element.getElementsByTagName(tagName))) {
13151          return fallback;
13152        }
13153      }
13154    } // Activates all listeners stored in @listeners
13155
13156  }, {
13157    key: "setupEventListeners",
13158    value: function setupEventListeners() {
13159      return this.listeners.map(function (elementListeners) {
13160        return function () {
13161          var result = [];
13162
13163          for (var event in elementListeners.events) {
13164            var listener = elementListeners.events[event];
13165            result.push(elementListeners.element.addEventListener(event, listener, false));
13166          }
13167
13168          return result;
13169        }();
13170      });
13171    } // Deactivates all listeners stored in @listeners
13172
13173  }, {
13174    key: "removeEventListeners",
13175    value: function removeEventListeners() {
13176      return this.listeners.map(function (elementListeners) {
13177        return function () {
13178          var result = [];
13179
13180          for (var event in elementListeners.events) {
13181            var listener = elementListeners.events[event];
13182            result.push(elementListeners.element.removeEventListener(event, listener, false));
13183          }
13184
13185          return result;
13186        }();
13187      });
13188    } // Removes all event listeners and cancels all files in the queue or being processed.
13189
13190  }, {
13191    key: "disable",
13192    value: function disable() {
13193      var _this4 = this;
13194
13195      this.clickableElements.forEach(function (element) {
13196        return element.classList.remove("dz-clickable");
13197      });
13198      this.removeEventListeners();
13199      this.disabled = true;
13200      return this.files.map(function (file) {
13201        return _this4.cancelUpload(file);
13202      });
13203    }
13204  }, {
13205    key: "enable",
13206    value: function enable() {
13207      delete this.disabled;
13208      this.clickableElements.forEach(function (element) {
13209        return element.classList.add("dz-clickable");
13210      });
13211      return this.setupEventListeners();
13212    } // Returns a nicely formatted filesize
13213
13214  }, {
13215    key: "filesize",
13216    value: function filesize(size) {
13217      var selectedSize = 0;
13218      var selectedUnit = "b";
13219
13220      if (size > 0) {
13221        var units = ['tb', 'gb', 'mb', 'kb', 'b'];
13222
13223        for (var i = 0; i < units.length; i++) {
13224          var unit = units[i];
13225          var cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10;
13226
13227          if (size >= cutoff) {
13228            selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i);
13229            selectedUnit = unit;
13230            break;
13231          }
13232        }
13233
13234        selectedSize = Math.round(10 * selectedSize) / 10; // Cutting of digits
13235      }
13236
13237      return "<strong>".concat(selectedSize, "</strong> ").concat(this.options.dictFileSizeUnits[selectedUnit]);
13238    } // Adds or removes the `dz-max-files-reached` class from the form.
13239
13240  }, {
13241    key: "_updateMaxFilesReachedClass",
13242    value: function _updateMaxFilesReachedClass() {
13243      if (this.options.maxFiles != null && this.getAcceptedFiles().length >= this.options.maxFiles) {
13244        if (this.getAcceptedFiles().length === this.options.maxFiles) {
13245          this.emit('maxfilesreached', this.files);
13246        }
13247
13248        return this.element.classList.add("dz-max-files-reached");
13249      } else {
13250        return this.element.classList.remove("dz-max-files-reached");
13251      }
13252    }
13253  }, {
13254    key: "drop",
13255    value: function drop(e) {
13256      if (!e.dataTransfer) {
13257        return;
13258      }
13259
13260      this.emit("drop", e); // Convert the FileList to an Array
13261      // This is necessary for IE11
13262
13263      var files = [];
13264
13265      for (var i = 0; i < e.dataTransfer.files.length; i++) {
13266        files[i] = e.dataTransfer.files[i];
13267      } // Even if it's a folder, files.length will contain the folders.
13268
13269
13270      if (files.length) {
13271        var items = e.dataTransfer.items;
13272
13273        if (items && items.length && items[0].webkitGetAsEntry != null) {
13274          // The browser supports dropping of folders, so handle items instead of files
13275          this._addFilesFromItems(items);
13276        } else {
13277          this.handleFiles(files);
13278        }
13279      }
13280
13281      this.emit("addedfiles", files);
13282    }
13283  }, {
13284    key: "paste",
13285    value: function paste(e) {
13286      if (__guard__(e != null ? e.clipboardData : undefined, function (x) {
13287        return x.items;
13288      }) == null) {
13289        return;
13290      }
13291
13292      this.emit("paste", e);
13293      var items = e.clipboardData.items;
13294
13295      if (items.length) {
13296        return this._addFilesFromItems(items);
13297      }
13298    }
13299  }, {
13300    key: "handleFiles",
13301    value: function handleFiles(files) {
13302      var _iteratorNormalCompletion13 = true;
13303      var _didIteratorError13 = false;
13304      var _iteratorError13 = undefined;
13305
13306      try {
13307        for (var _iterator13 = files[Symbol.iterator](), _step13; !(_iteratorNormalCompletion13 = (_step13 = _iterator13.next()).done); _iteratorNormalCompletion13 = true) {
13308          var file = _step13.value;
13309          this.addFile(file);
13310        }
13311      } catch (err) {
13312        _didIteratorError13 = true;
13313        _iteratorError13 = err;
13314      } finally {
13315        try {
13316          if (!_iteratorNormalCompletion13 && _iterator13["return"] != null) {
13317            _iterator13["return"]();
13318          }
13319        } finally {
13320          if (_didIteratorError13) {
13321            throw _iteratorError13;
13322          }
13323        }
13324      }
13325    } // When a folder is dropped (or files are pasted), items must be handled
13326    // instead of files.
13327
13328  }, {
13329    key: "_addFilesFromItems",
13330    value: function _addFilesFromItems(items) {
13331      var _this5 = this;
13332
13333      return function () {
13334        var result = [];
13335        var _iteratorNormalCompletion14 = true;
13336        var _didIteratorError14 = false;
13337        var _iteratorError14 = undefined;
13338
13339        try {
13340          for (var _iterator14 = items[Symbol.iterator](), _step14; !(_iteratorNormalCompletion14 = (_step14 = _iterator14.next()).done); _iteratorNormalCompletion14 = true) {
13341            var item = _step14.value;
13342            var entry;
13343
13344            if (item.webkitGetAsEntry != null && (entry = item.webkitGetAsEntry())) {
13345              if (entry.isFile) {
13346                result.push(_this5.addFile(item.getAsFile()));
13347              } else if (entry.isDirectory) {
13348                // Append all files from that directory to files
13349                result.push(_this5._addFilesFromDirectory(entry, entry.name));
13350              } else {
13351                result.push(undefined);
13352              }
13353            } else if (item.getAsFile != null) {
13354              if (item.kind == null || item.kind === "file") {
13355                result.push(_this5.addFile(item.getAsFile()));
13356              } else {
13357                result.push(undefined);
13358              }
13359            } else {
13360              result.push(undefined);
13361            }
13362          }
13363        } catch (err) {
13364          _didIteratorError14 = true;
13365          _iteratorError14 = err;
13366        } finally {
13367          try {
13368            if (!_iteratorNormalCompletion14 && _iterator14["return"] != null) {
13369              _iterator14["return"]();
13370            }
13371          } finally {
13372            if (_didIteratorError14) {
13373              throw _iteratorError14;
13374            }
13375          }
13376        }
13377
13378        return result;
13379      }();
13380    } // Goes through the directory, and adds each file it finds recursively
13381
13382  }, {
13383    key: "_addFilesFromDirectory",
13384    value: function _addFilesFromDirectory(directory, path) {
13385      var _this6 = this;
13386
13387      var dirReader = directory.createReader();
13388
13389      var errorHandler = function errorHandler(error) {
13390        return __guardMethod__(console, 'log', function (o) {
13391          return o.log(error);
13392        });
13393      };
13394
13395      var readEntries = function readEntries() {
13396        return dirReader.readEntries(function (entries) {
13397          if (entries.length > 0) {
13398            var _iteratorNormalCompletion15 = true;
13399            var _didIteratorError15 = false;
13400            var _iteratorError15 = undefined;
13401
13402            try {
13403              for (var _iterator15 = entries[Symbol.iterator](), _step15; !(_iteratorNormalCompletion15 = (_step15 = _iterator15.next()).done); _iteratorNormalCompletion15 = true) {
13404                var entry = _step15.value;
13405
13406                if (entry.isFile) {
13407                  entry.file(function (file) {
13408                    if (_this6.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') {
13409                      return;
13410                    }
13411
13412                    file.fullPath = "".concat(path, "/").concat(file.name);
13413                    return _this6.addFile(file);
13414                  });
13415                } else if (entry.isDirectory) {
13416                  _this6._addFilesFromDirectory(entry, "".concat(path, "/").concat(entry.name));
13417                }
13418              } // Recursively call readEntries() again, since browser only handle
13419              // the first 100 entries.
13420              // See: https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries
13421
13422            } catch (err) {
13423              _didIteratorError15 = true;
13424              _iteratorError15 = err;
13425            } finally {
13426              try {
13427                if (!_iteratorNormalCompletion15 && _iterator15["return"] != null) {
13428                  _iterator15["return"]();
13429                }
13430              } finally {
13431                if (_didIteratorError15) {
13432                  throw _iteratorError15;
13433                }
13434              }
13435            }
13436
13437            readEntries();
13438          }
13439
13440          return null;
13441        }, errorHandler);
13442      };
13443
13444      return readEntries();
13445    } // If `done()` is called without argument the file is accepted
13446    // If you call it with an error message, the file is rejected
13447    // (This allows for asynchronous validation)
13448    //
13449    // This function checks the filesize, and if the file.type passes the
13450    // `acceptedFiles` check.
13451
13452  }, {
13453    key: "accept",
13454    value: function accept(file, done) {
13455      if (this.options.maxFilesize && file.size > this.options.maxFilesize * 1024 * 1024) {
13456        done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize));
13457      } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) {
13458        done(this.options.dictInvalidFileType);
13459      } else if (this.options.maxFiles != null && this.getAcceptedFiles().length >= this.options.maxFiles) {
13460        done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles));
13461        this.emit("maxfilesexceeded", file);
13462      } else {
13463        this.options.accept.call(this, file, done);
13464      }
13465    }
13466  }, {
13467    key: "addFile",
13468    value: function addFile(file) {
13469      var _this7 = this;
13470
13471      file.upload = {
13472        uuid: Dropzone.uuidv4(),
13473        progress: 0,
13474        // Setting the total upload size to file.size for the beginning
13475        // It's actual different than the size to be transmitted.
13476        total: file.size,
13477        bytesSent: 0,
13478        filename: this._renameFile(file) // Not setting chunking information here, because the acutal data — and
13479        // thus the chunks — might change if `options.transformFile` is set
13480        // and does something to the data.
13481
13482      };
13483      this.files.push(file);
13484      file.status = Dropzone.ADDED;
13485      this.emit("addedfile", file);
13486
13487      this._enqueueThumbnail(file);
13488
13489      this.accept(file, function (error) {
13490        if (error) {
13491          file.accepted = false;
13492
13493          _this7._errorProcessing([file], error); // Will set the file.status
13494
13495        } else {
13496          file.accepted = true;
13497
13498          if (_this7.options.autoQueue) {
13499            _this7.enqueueFile(file);
13500          } // Will set .accepted = true
13501
13502        }
13503
13504        _this7._updateMaxFilesReachedClass();
13505      });
13506    } // Wrapper for enqueueFile
13507
13508  }, {
13509    key: "enqueueFiles",
13510    value: function enqueueFiles(files) {
13511      var _iteratorNormalCompletion16 = true;
13512      var _didIteratorError16 = false;
13513      var _iteratorError16 = undefined;
13514
13515      try {
13516        for (var _iterator16 = files[Symbol.iterator](), _step16; !(_iteratorNormalCompletion16 = (_step16 = _iterator16.next()).done); _iteratorNormalCompletion16 = true) {
13517          var file = _step16.value;
13518          this.enqueueFile(file);
13519        }
13520      } catch (err) {
13521        _didIteratorError16 = true;
13522        _iteratorError16 = err;
13523      } finally {
13524        try {
13525          if (!_iteratorNormalCompletion16 && _iterator16["return"] != null) {
13526            _iterator16["return"]();
13527          }
13528        } finally {
13529          if (_didIteratorError16) {
13530            throw _iteratorError16;
13531          }
13532        }
13533      }
13534
13535      return null;
13536    }
13537  }, {
13538    key: "enqueueFile",
13539    value: function enqueueFile(file) {
13540      var _this8 = this;
13541
13542      if (file.status === Dropzone.ADDED && file.accepted === true) {
13543        file.status = Dropzone.QUEUED;
13544
13545        if (this.options.autoProcessQueue) {
13546          return setTimeout(function () {
13547            return _this8.processQueue();
13548          }, 0); // Deferring the call
13549        }
13550      } else {
13551        throw new Error("This file can't be queued because it has already been processed or was rejected.");
13552      }
13553    }
13554  }, {
13555    key: "_enqueueThumbnail",
13556    value: function _enqueueThumbnail(file) {
13557      var _this9 = this;
13558
13559      if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) {
13560        this._thumbnailQueue.push(file);
13561
13562        return setTimeout(function () {
13563          return _this9._processThumbnailQueue();
13564        }, 0); // Deferring the call
13565      }
13566    }
13567  }, {
13568    key: "_processThumbnailQueue",
13569    value: function _processThumbnailQueue() {
13570      var _this10 = this;
13571
13572      if (this._processingThumbnail || this._thumbnailQueue.length === 0) {
13573        return;
13574      }
13575
13576      this._processingThumbnail = true;
13577
13578      var file = this._thumbnailQueue.shift();
13579
13580      return this.createThumbnail(file, this.options.thumbnailWidth, this.options.thumbnailHeight, this.options.thumbnailMethod, true, function (dataUrl) {
13581        _this10.emit("thumbnail", file, dataUrl);
13582
13583        _this10._processingThumbnail = false;
13584        return _this10._processThumbnailQueue();
13585      });
13586    } // Can be called by the user to remove a file
13587
13588  }, {
13589    key: "removeFile",
13590    value: function removeFile(file) {
13591      if (file.status === Dropzone.UPLOADING) {
13592        this.cancelUpload(file);
13593      }
13594
13595      this.files = without(this.files, file);
13596      this.emit("removedfile", file);
13597
13598      if (this.files.length === 0) {
13599        return this.emit("reset");
13600      }
13601    } // Removes all files that aren't currently processed from the list
13602
13603  }, {
13604    key: "removeAllFiles",
13605    value: function removeAllFiles(cancelIfNecessary) {
13606      // Create a copy of files since removeFile() changes the @files array.
13607      if (cancelIfNecessary == null) {
13608        cancelIfNecessary = false;
13609      }
13610
13611      var _iteratorNormalCompletion17 = true;
13612      var _didIteratorError17 = false;
13613      var _iteratorError17 = undefined;
13614
13615      try {
13616        for (var _iterator17 = this.files.slice()[Symbol.iterator](), _step17; !(_iteratorNormalCompletion17 = (_step17 = _iterator17.next()).done); _iteratorNormalCompletion17 = true) {
13617          var file = _step17.value;
13618
13619          if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) {
13620            this.removeFile(file);
13621          }
13622        }
13623      } catch (err) {
13624        _didIteratorError17 = true;
13625        _iteratorError17 = err;
13626      } finally {
13627        try {
13628          if (!_iteratorNormalCompletion17 && _iterator17["return"] != null) {
13629            _iterator17["return"]();
13630          }
13631        } finally {
13632          if (_didIteratorError17) {
13633            throw _iteratorError17;
13634          }
13635        }
13636      }
13637
13638      return null;
13639    } // Resizes an image before it gets sent to the server. This function is the default behavior of
13640    // `options.transformFile` if `resizeWidth` or `resizeHeight` are set. The callback is invoked with
13641    // the resized blob.
13642
13643  }, {
13644    key: "resizeImage",
13645    value: function resizeImage(file, width, height, resizeMethod, callback) {
13646      var _this11 = this;
13647
13648      return this.createThumbnail(file, width, height, resizeMethod, true, function (dataUrl, canvas) {
13649        if (canvas == null) {
13650          // The image has not been resized
13651          return callback(file);
13652        } else {
13653          var resizeMimeType = _this11.options.resizeMimeType;
13654
13655          if (resizeMimeType == null) {
13656            resizeMimeType = file.type;
13657          }
13658
13659          var resizedDataURL = canvas.toDataURL(resizeMimeType, _this11.options.resizeQuality);
13660
13661          if (resizeMimeType === 'image/jpeg' || resizeMimeType === 'image/jpg') {
13662            // Now add the original EXIF information
13663            resizedDataURL = ExifRestore.restore(file.dataURL, resizedDataURL);
13664          }
13665
13666          return callback(Dropzone.dataURItoBlob(resizedDataURL));
13667        }
13668      });
13669    }
13670  }, {
13671    key: "createThumbnail",
13672    value: function createThumbnail(file, width, height, resizeMethod, fixOrientation, callback) {
13673      var _this12 = this;
13674
13675      var fileReader = new FileReader();
13676
13677      fileReader.onload = function () {
13678        file.dataURL = fileReader.result; // Don't bother creating a thumbnail for SVG images since they're vector
13679
13680        if (file.type === "image/svg+xml") {
13681          if (callback != null) {
13682            callback(fileReader.result);
13683          }
13684
13685          return;
13686        }
13687
13688        _this12.createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback);
13689      };
13690
vendor: 5,819 bytes, lines 13691-13869
13691      fileReader.readAsDataURL(file);
13692    } // `mockFile` needs to have these attributes:
13693    // 
13694    //     { name: 'name', size: 12345, imageUrl: '' }
13695    //
13696    // `callback` will be invoked when the image has been downloaded and displayed.
13697    // `crossOrigin` will be added to the `img` tag when accessing the file.
13698
13699  }, {
13700    key: "displayExistingFile",
13701    value: function displayExistingFile(mockFile, imageUrl, callback, crossOrigin) {
13702      var _this13 = this;
13703
13704      var resizeThumbnail = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
13705      this.emit("addedfile", mockFile);
13706      this.emit("complete", mockFile);
13707
13708      if (!resizeThumbnail) {
13709        this.emit("thumbnail", mockFile, imageUrl);
13710        if (callback) callback();
13711      } else {
13712        var onDone = function onDone(thumbnail) {
13713          _this13.emit('thumbnail', mockFile, thumbnail);
13714
13715          if (callback) callback();
13716        };
13717
13718        mockFile.dataURL = imageUrl;
13719        this.createThumbnailFromUrl(mockFile, this.options.thumbnailWidth, this.options.thumbnailHeight, this.options.resizeMethod, this.options.fixOrientation, onDone, crossOrigin);
13720      }
13721    }
13722  }, {
13723    key: "createThumbnailFromUrl",
13724    value: function createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback, crossOrigin) {
13725      var _this14 = this;
13726
13727      // Not using `new Image` here because of a bug in latest Chrome versions.
13728      // See https://github.com/enyo/dropzone/pull/226
13729      var img = document.createElement("img");
13730
13731      if (crossOrigin) {
13732        img.crossOrigin = crossOrigin;
13733      }
13734
13735      img.onload = function () {
13736        var loadExif = function loadExif(callback) {
13737          return callback(1);
13738        };
13739
13740        if (typeof EXIF !== 'undefined' && EXIF !== null && fixOrientation) {
13741          loadExif = function loadExif(callback) {
13742            return EXIF.getData(img, function () {
13743              return callback(EXIF.getTag(this, 'Orientation'));
13744            });
13745          };
13746        }
13747
13748        return loadExif(function (orientation) {
13749          file.width = img.width;
13750          file.height = img.height;
13751
13752          var resizeInfo = _this14.options.resize.call(_this14, file, width, height, resizeMethod);
13753
13754          var canvas = document.createElement("canvas");
13755          var ctx = canvas.getContext("2d");
13756          canvas.width = resizeInfo.trgWidth;
13757          canvas.height = resizeInfo.trgHeight;
13758
13759          if (orientation > 4) {
13760            canvas.width = resizeInfo.trgHeight;
13761            canvas.height = resizeInfo.trgWidth;
13762          }
13763
13764          switch (orientation) {
13765            case 2:
13766              // horizontal flip
13767              ctx.translate(canvas.width, 0);
13768              ctx.scale(-1, 1);
13769              break;
13770
13771            case 3:
13772              // 180° rotate left
13773              ctx.translate(canvas.width, canvas.height);
13774              ctx.rotate(Math.PI);
13775              break;
13776
13777            case 4:
13778              // vertical flip
13779              ctx.translate(0, canvas.height);
13780              ctx.scale(1, -1);
13781              break;
13782
13783            case 5:
13784              // vertical flip + 90 rotate right
13785              ctx.rotate(0.5 * Math.PI);
13786              ctx.scale(1, -1);
13787              break;
13788
13789            case 6:
13790              // 90° rotate right
13791              ctx.rotate(0.5 * Math.PI);
13792              ctx.translate(0, -canvas.width);
13793              break;
13794
13795            case 7:
13796              // horizontal flip + 90 rotate right
13797              ctx.rotate(0.5 * Math.PI);
13798              ctx.translate(canvas.height, -canvas.width);
13799              ctx.scale(-1, 1);
13800              break;
13801
13802            case 8:
13803              // 90° rotate left
13804              ctx.rotate(-0.5 * Math.PI);
13805              ctx.translate(-canvas.height, 0);
13806              break;
13807          } // This is a bugfix for iOS' scaling bug.
13808
13809
13810          drawImageIOSFix(ctx, img, resizeInfo.srcX != null ? resizeInfo.srcX : 0, resizeInfo.srcY != null ? resizeInfo.srcY : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, resizeInfo.trgX != null ? resizeInfo.trgX : 0, resizeInfo.trgY != null ? resizeInfo.trgY : 0, resizeInfo.trgWidth, resizeInfo.trgHeight);
13811          var thumbnail = canvas.toDataURL("image/png");
13812
13813          if (callback != null) {
13814            return callback(thumbnail, canvas);
13815          }
13816        });
13817      };
13818
13819      if (callback != null) {
13820        img.onerror = callback;
13821      }
13822
13823      return img.src = file.dataURL;
13824    } // Goes through the queue and processes files if there aren't too many already.
13825
13826  }, {
13827    key: "processQueue",
13828    value: function processQueue() {
13829      var parallelUploads = this.options.parallelUploads;
13830      var processingLength = this.getUploadingFiles().length;
13831      var i = processingLength; // There are already at least as many files uploading than should be
13832
13833      if (processingLength >= parallelUploads) {
13834        return;
13835      }
13836
13837      var queuedFiles = this.getQueuedFiles();
13838
13839      if (!(queuedFiles.length > 0)) {
13840        return;
13841      }
13842
13843      if (this.options.uploadMultiple) {
13844        // The files should be uploaded in one request
13845        return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength));
13846      } else {
13847        while (i < parallelUploads) {
13848          if (!queuedFiles.length) {
13849            return;
13850          } // Nothing left to process
13851
13852
13853          this.processFile(queuedFiles.shift());
13854          i++;
13855        }
13856      }
13857    } // Wrapper for `processFiles`
13858
13859  }, {
13860    key: "processFile",
13861    value: function processFile(file) {
13862      return this.processFiles([file]);
13863    } // Loads the file, then calls finishedLoading()
13864
13865  }, {
13866    key: "processFiles",
13867    value: function processFiles(files) {
13868      var _iteratorNormalCompletion18 = true;
13869      var _didIteratorError18 = false;
13870      var _iteratorError18 = undefined;
13871
13872      try {
13873        for (var _iterator18 = files[Symbol.iterator](), _step18; !(_iteratorNormalCompletion18 = (_step18 = _iterator18.next()).done); _iteratorNormalCompletion18 = true) {
13874          var file = _step18.value;
13875          file.processing = true; // Backwards compatibility
13876
13877          file.status = Dropzone.UPLOADING;
13878          this.emit("processing", file);
13879        }
13880      } catch (err) {
13881        _didIteratorError18 = true;
13882        _iteratorError18 = err;
13883      } finally {
13884        try {
13885          if (!_iteratorNormalCompletion18 && _iterator18["return"] != null) {
13886            _iterator18["return"]();
13887          }
13888        } finally {
13889          if (_didIteratorError18) {
13890            throw _iteratorError18;
13891          }
13892        }
13893      }
13894
13895      if (this.options.uploadMultiple) {
13896        this.emit("processingmultiple", files);
13897      }
13898
13899      return this.uploadFiles(files);
13900    }
13901  }, {
13902    key: "_getFilesWithXhr",
13903    value: function _getFilesWithXhr(xhr) {
13904      var files;
13905      return files = this.files.filter(function (file) {
13906        return file.xhr === xhr;
13907      }).map(function (file) {
13908        return file;
13909      });
13910    } // Cancels the file upload and sets the status to CANCELED
13911    // **if** the file is actually being uploaded.
13912    // If it's still in the queue, the file is being removed from it and the status
13913    // set to CANCELED.
13914
13915  }, {
13916    key: "cancelUpload",
13917    value: function cancelUpload(file) {
13918      if (file.status === Dropzone.UPLOADING) {
13919        var groupedFiles = this._getFilesWithXhr(file.xhr);
13920
13921        var _iteratorNormalCompletion19 = true;
13922        var _didIteratorError19 = false;
13923        var _iteratorError19 = undefined;
13924
13925        try {
13926          for (var _iterator19 = groupedFiles[Symbol.iterator](), _step19; !(_iteratorNormalCompletion19 = (_step19 = _iterator19.next()).done); _iteratorNormalCompletion19 = true) {
13927            var groupedFile = _step19.value;
13928            groupedFile.status = Dropzone.CANCELED;
13929          }
13930        } catch (err) {
13931          _didIteratorError19 = true;
13932          _iteratorError19 = err;
13933        } finally {
13934          try {
13935            if (!_iteratorNormalCompletion19 && _iterator19["return"] != null) {
13936              _iterator19["return"]();
13937            }
13938          } finally {
13939            if (_didIteratorError19) {
13940              throw _iteratorError19;
13941            }
13942          }
13943        }
13944
13945        if (typeof file.xhr !== 'undefined') {
13946          file.xhr.abort();
13947        }
13948
13949        var _iteratorNormalCompletion20 = true;
13950        var _didIteratorError20 = false;
13951        var _iteratorError20 = undefined;
13952
13953        try {
13954          for (var _iterator20 = groupedFiles[Symbol.iterator](), _step20; !(_iteratorNormalCompletion20 = (_step20 = _iterator20.next()).done); _iteratorNormalCompletion20 = true) {
13955            var _groupedFile = _step20.value;
13956            this.emit("canceled", _groupedFile);
13957          }
13958        } catch (err) {
13959          _didIteratorError20 = true;
13960          _iteratorError20 = err;
13961        } finally {
13962          try {
13963            if (!_iteratorNormalCompletion20 && _iterator20["return"] != null) {
13964              _iterator20["return"]();
13965            }
13966          } finally {
13967            if (_didIteratorError20) {
13968              throw _iteratorError20;
13969            }
13970          }
13971        }
13972
13973        if (this.options.uploadMultiple) {
13974          this.emit("canceledmultiple", groupedFiles);
13975        }
13976      } else if (file.status === Dropzone.ADDED || file.status === Dropzone.QUEUED) {
13977        file.status = Dropzone.CANCELED;
13978        this.emit("canceled", file);
13979
13980        if (this.options.uploadMultiple) {
13981          this.emit("canceledmultiple", [file]);
13982        }
13983      }
13984
13985      if (this.options.autoProcessQueue) {
13986        return this.processQueue();
13987      }
13988    }
13989  }, {
13990    key: "resolveOption",
13991    value: function resolveOption(option) {
13992      if (typeof option === 'function') {
13993        for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
13994          args[_key3 - 1] = arguments[_key3];
13995        }
13996
13997        return option.apply(this, args);
13998      }
13999
14000      return option;
14001    }
14002  }, {
14003    key: "uploadFile",
14004    value: function uploadFile(file) {
14005      return this.uploadFiles([file]);
14006    }
14007  }, {
14008    key: "uploadFiles",
14009    value: function uploadFiles(files) {
14010      var _this15 = this;
14011
14012      this._transformFiles(files, function (transformedFiles) {
14013        if (_this15.options.chunking) {
14014          // Chunking is not allowed to be used with `uploadMultiple` so we know
14015          // that there is only __one__file.
14016          var transformedFile = transformedFiles[0];
14017          files[0].upload.chunked = _this15.options.chunking && (_this15.options.forceChunking || transformedFile.size > _this15.options.chunkSize);
vendor: 4,150 bytes, lines 14018-14130
14018          files[0].upload.totalChunkCount = Math.ceil(transformedFile.size / _this15.options.chunkSize);
14019        }
14020
14021        if (files[0].upload.chunked) {
14022          // This file should be sent in chunks!
14023          // If the chunking option is set, we **know** that there can only be **one** file, since
14024          // uploadMultiple is not allowed with this option.
14025          var file = files[0];
14026          var _transformedFile = transformedFiles[0];
14027          var startedChunkCount = 0;
14028          file.upload.chunks = [];
14029
14030          var handleNextChunk = function handleNextChunk() {
14031            var chunkIndex = 0; // Find the next item in file.upload.chunks that is not defined yet.
14032
14033            while (file.upload.chunks[chunkIndex] !== undefined) {
14034              chunkIndex++;
14035            } // This means, that all chunks have already been started.
14036
14037
14038            if (chunkIndex >= file.upload.totalChunkCount) return;
14039            startedChunkCount++;
14040            var start = chunkIndex * _this15.options.chunkSize;
14041            var end = Math.min(start + _this15.options.chunkSize, file.size);
14042            var dataBlock = {
14043              name: _this15._getParamName(0),
14044              data: _transformedFile.webkitSlice ? _transformedFile.webkitSlice(start, end) : _transformedFile.slice(start, end),
14045              filename: file.upload.filename,
14046              chunkIndex: chunkIndex
14047            };
14048            file.upload.chunks[chunkIndex] = {
14049              file: file,
14050              index: chunkIndex,
14051              dataBlock: dataBlock,
14052              // In case we want to retry.
14053              status: Dropzone.UPLOADING,
14054              progress: 0,
14055              retries: 0 // The number of times this block has been retried.
14056
14057            };
14058
14059            _this15._uploadData(files, [dataBlock]);
14060          };
14061
14062          file.upload.finishedChunkUpload = function (chunk) {
14063            var allFinished = true;
14064            chunk.status = Dropzone.SUCCESS; // Clear the data from the chunk
14065
14066            chunk.dataBlock = null; // Leaving this reference to xhr intact here will cause memory leaks in some browsers
14067
14068            chunk.xhr = null;
14069
14070            for (var i = 0; i < file.upload.totalChunkCount; i++) {
14071              if (file.upload.chunks[i] === undefined) {
14072                return handleNextChunk();
14073              }
14074
14075              if (file.upload.chunks[i].status !== Dropzone.SUCCESS) {
14076                allFinished = false;
14077              }
14078            }
14079
14080            if (allFinished) {
14081              _this15.options.chunksUploaded(file, function () {
14082                _this15._finished(files, '', null);
14083              });
14084            }
14085          };
14086
14087          if (_this15.options.parallelChunkUploads) {
14088            for (var i = 0; i < file.upload.totalChunkCount; i++) {
14089              handleNextChunk();
14090            }
14091          } else {
14092            handleNextChunk();
14093          }
14094        } else {
14095          var dataBlocks = [];
14096
14097          for (var _i3 = 0; _i3 < files.length; _i3++) {
14098            dataBlocks[_i3] = {
14099              name: _this15._getParamName(_i3),
14100              data: transformedFiles[_i3],
14101              filename: files[_i3].upload.filename
14102            };
14103          }
14104
14105          _this15._uploadData(files, dataBlocks);
14106        }
14107      });
14108    } /// Returns the right chunk for given file and xhr
14109
14110  }, {
14111    key: "_getChunk",
14112    value: function _getChunk(file, xhr) {
14113      for (var i = 0; i < file.upload.totalChunkCount; i++) {
14114        if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].xhr === xhr) {
14115          return file.upload.chunks[i];
14116        }
14117      }
14118    } // This function actually uploads the file(s) to the server.
14119    // If dataBlocks contains the actual data to upload (meaning, that this could either be transformed
14120    // files, or individual chunks for chunked upload).
14121
14122  }, {
14123    key: "_uploadData",
14124    value: function _uploadData(files, dataBlocks) {
14125      var _this16 = this;
14126
14127      var xhr = new XMLHttpRequest(); // Put the xhr object in the file objects to be able to reference it later.
14128
14129      var _iteratorNormalCompletion21 = true;
14130      var _didIteratorError21 = false;
14131      var _iteratorError21 = undefined;
14132
14133      try {
14134        for (var _iterator21 = files[Symbol.iterator](), _step21; !(_iteratorNormalCompletion21 = (_step21 = _iterator21.next()).done); _iteratorNormalCompletion21 = true) {
14135          var file = _step21.value;
14136          file.xhr = xhr;
14137        }
14138      } catch (err) {
14139        _didIteratorError21 = true;
14140        _iteratorError21 = err;
14141      } finally {
14142        try {
14143          if (!_iteratorNormalCompletion21 && _iterator21["return"] != null) {
14144            _iterator21["return"]();
14145          }
14146        } finally {
14147          if (_didIteratorError21) {
14148            throw _iteratorError21;
14149          }
14150        }
14151      }
14152
14153      if (files[0].upload.chunked) {
14154        // Put the xhr object in the right chunk object, so it can be associated later, and found with _getChunk
14155        files[0].upload.chunks[dataBlocks[0].chunkIndex].xhr = xhr;
14156      }
14157
14158      var method = this.resolveOption(this.options.method, files);
14159      var url = this.resolveOption(this.options.url, files);
14160      xhr.open(method, url, true); // Setting the timeout after open because of IE11 issue: https://gitlab.com/meno/dropzone/issues/8
14161
14162      xhr.timeout = this.resolveOption(this.options.timeout, files); // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
14163
14164      xhr.withCredentials = !!this.options.withCredentials;
14165
14166      xhr.onload = function (e) {
14167        _this16._finishedUploading(files, xhr, e);
14168      };
14169
14170      xhr.ontimeout = function () {
14171        _this16._handleUploadError(files, xhr, "Request timedout after ".concat(_this16.options.timeout, " seconds"));
14172      };
14173
14174      xhr.onerror = function () {
14175        _this16._handleUploadError(files, xhr);
14176      }; // Some browsers do not have the .upload property
14177
14178
14179      var progressObj = xhr.upload != null ? xhr.upload : xhr;
14180
14181      progressObj.onprogress = function (e) {
14182        return _this16._updateFilesUploadProgress(files, xhr, e);
14183      };
14184
14185      var headers = {
14186        "Accept": "application/json",
14187        "Cache-Control": "no-cache",
14188        "X-Requested-With": "XMLHttpRequest"
14189      };
14190
14191      if (this.options.headers) {
14192        Dropzone.extend(headers, this.options.headers);
14193      }
14194
14195      for (var headerName in headers) {
14196        var headerValue = headers[headerName];
14197
14198        if (headerValue) {
14199          xhr.setRequestHeader(headerName, headerValue);
14200        }
14201      }
14202
14203      var formData = new FormData(); // Adding all @options parameters
14204
14205      if (this.options.params) {
14206        var additionalParams = this.options.params;
14207
14208        if (typeof additionalParams === 'function') {
14209          additionalParams = additionalParams.call(this, files, xhr, files[0].upload.chunked ? this._getChunk(files[0], xhr) : null);
14210        }
14211
14212        for (var key in additionalParams) {
14213          var value = additionalParams[key];
14214          formData.append(key, value);
14215        }
14216      } // Let the user add additional data if necessary
14217
14218
14219      var _iteratorNormalCompletion22 = true;
14220      var _didIteratorError22 = false;
14221      var _iteratorError22 = undefined;
14222
14223      try {
14224        for (var _iterator22 = files[Symbol.iterator](), _step22; !(_iteratorNormalCompletion22 = (_step22 = _iterator22.next()).done); _iteratorNormalCompletion22 = true) {
14225          var _file = _step22.value;
14226          this.emit("sending", _file, xhr, formData);
14227        }
14228      } catch (err) {
14229        _didIteratorError22 = true;
14230        _iteratorError22 = err;
14231      } finally {
14232        try {
14233          if (!_iteratorNormalCompletion22 && _iterator22["return"] != null) {
14234            _iterator22["return"]();
14235          }
14236        } finally {
14237          if (_didIteratorError22) {
14238            throw _iteratorError22;
14239          }
14240        }
14241      }
14242
14243      if (this.options.uploadMultiple) {
14244        this.emit("sendingmultiple", files, xhr, formData);
14245      }
14246
14247      this._addFormElementData(formData); // Finally add the files
14248      // Has to be last because some servers (eg: S3) expect the file to be the last parameter
14249
14250
14251      for (var i = 0; i < dataBlocks.length; i++) {
14252        var dataBlock = dataBlocks[i];
14253        formData.append(dataBlock.name, dataBlock.data, dataBlock.filename);
14254      }
14255
14256      this.submitRequest(xhr, formData, files);
14257    } // Transforms all files with this.options.transformFile and invokes done with the transformed files when done.
14258
14259  }, {
14260    key: "_transformFiles",
14261    value: function _transformFiles(files, done) {
14262      var _this17 = this;
14263
14264      var transformedFiles = []; // Clumsy way of handling asynchronous calls, until I get to add a proper Future library.
14265
14266      var doneCounter = 0;
14267
14268      var _loop = function _loop(i) {
14269        _this17.options.transformFile.call(_this17, files[i], function (transformedFile) {
14270          transformedFiles[i] = transformedFile;
14271
14272          if (++doneCounter === files.length) {
14273            done(transformedFiles);
14274          }
14275        });
14276      };
14277
14278      for (var i = 0; i < files.length; i++) {
14279        _loop(i);
14280      }
14281    } // Takes care of adding other input elements of the form to the AJAX request
14282
14283  }, {
14284    key: "_addFormElementData",
14285    value: function _addFormElementData(formData) {
14286      // Take care of other input elements
14287      if (this.element.tagName === "FORM") {
14288        var _iteratorNormalCompletion23 = true;
14289        var _didIteratorError23 = false;
14290        var _iteratorError23 = undefined;
14291
14292        try {
14293          for (var _iterator23 = this.element.querySelectorAll("input, textarea, select, button")[Symbol.iterator](), _step23; !(_iteratorNormalCompletion23 = (_step23 = _iterator23.next()).done); _iteratorNormalCompletion23 = true) {
14294            var input = _step23.value;
14295            var inputName = input.getAttribute("name");
14296            var inputType = input.getAttribute("type");
14297            if (inputType) inputType = inputType.toLowerCase(); // If the input doesn't have a name, we can't use it.
14298
14299            if (typeof inputName === 'undefined' || inputName === null) continue;
14300
14301            if (input.tagName === "SELECT" && input.hasAttribute("multiple")) {
14302              // Possibly multiple values
14303              var _iteratorNormalCompletion24 = true;
14304              var _didIteratorError24 = false;
14305              var _iteratorError24 = undefined;
14306
14307              try {
14308                for (var _iterator24 = input.options[Symbol.iterator](), _step24; !(_iteratorNormalCompletion24 = (_step24 = _iterator24.next()).done); _iteratorNormalCompletion24 = true) {
14309                  var option = _step24.value;
14310
14311                  if (option.selected) {
14312                    formData.append(inputName, option.value);
14313                  }
14314                }
14315              } catch (err) {
14316                _didIteratorError24 = true;
14317                _iteratorError24 = err;
14318              } finally {
14319                try {
14320                  if (!_iteratorNormalCompletion24 && _iterator24["return"] != null) {
14321                    _iterator24["return"]();
14322                  }
14323                } finally {
14324                  if (_didIteratorError24) {
14325                    throw _iteratorError24;
14326                  }
14327                }
14328              }
14329            } else if (!inputType || inputType !== "checkbox" && inputType !== "radio" || input.checked) {
14330              formData.append(inputName, input.value);
14331            }
14332          }
14333        } catch (err) {
14334          _didIteratorError23 = true;
14335          _iteratorError23 = err;
14336        } finally {
14337          try {
14338            if (!_iteratorNormalCompletion23 && _iterator23["return"] != null) {
14339              _iterator23["return"]();
14340            }
14341          } finally {
14342            if (_didIteratorError23) {
14343              throw _iteratorError23;
14344            }
14345          }
14346        }
14347      }
14348    } // Invoked when there is new progress information about given files.
14349    // If e is not provided, it is assumed that the upload is finished.
14350
14351  }, {
14352    key: "_updateFilesUploadProgress",
14353    value: function _updateFilesUploadProgress(files, xhr, e) {
14354      var progress;
14355
14356      if (typeof e !== 'undefined') {
14357        progress = 100 * e.loaded / e.total;
14358
14359        if (files[0].upload.chunked) {
14360          var file = files[0]; // Since this is a chunked upload, we need to update the appropriate chunk progress.
14361
14362          var chunk = this._getChunk(file, xhr);
14363
14364          chunk.progress = progress;
14365          chunk.total = e.total;
14366          chunk.bytesSent = e.loaded;
14367          var fileProgress = 0,
14368              fileTotal,
14369              fileBytesSent;
14370          file.upload.progress = 0;
14371          file.upload.total = 0;
14372          file.upload.bytesSent = 0;
14373
14374          for (var i = 0; i < file.upload.totalChunkCount; i++) {
14375            if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].progress !== undefined) {
14376              file.upload.progress += file.upload.chunks[i].progress;
14377              file.upload.total += file.upload.chunks[i].total;
14378              file.upload.bytesSent += file.upload.chunks[i].bytesSent;
14379            }
14380          }
14381
14382          file.upload.progress = file.upload.progress / file.upload.totalChunkCount;
14383        } else {
14384          var _iteratorNormalCompletion25 = true;
14385          var _didIteratorError25 = false;
14386          var _iteratorError25 = undefined;
14387
14388          try {
14389            for (var _iterator25 = files[Symbol.iterator](), _step25; !(_iteratorNormalCompletion25 = (_step25 = _iterator25.next()).done); _iteratorNormalCompletion25 = true) {
14390              var _file2 = _step25.value;
14391              _file2.upload.progress = progress;
14392              _file2.upload.total = e.total;
14393              _file2.upload.bytesSent = e.loaded;
14394            }
14395          } catch (err) {
14396            _didIteratorError25 = true;
14397            _iteratorError25 = err;
14398          } finally {
14399            try {
14400              if (!_iteratorNormalCompletion25 && _iterator25["return"] != null) {
14401                _iterator25["return"]();
14402              }
14403            } finally {
14404              if (_didIteratorError25) {
14405                throw _iteratorError25;
14406              }
14407            }
14408          }
14409        }
14410
14411        var _iteratorNormalCompletion26 = true;
14412        var _didIteratorError26 = false;
14413        var _iteratorError26 = undefined;
14414
14415        try {
14416          for (var _iterator26 = files[Symbol.iterator](), _step26; !(_iteratorNormalCompletion26 = (_step26 = _iterator26.next()).done); _iteratorNormalCompletion26 = true) {
14417            var _file3 = _step26.value;
14418            this.emit("uploadprogress", _file3, _file3.upload.progress, _file3.upload.bytesSent);
14419          }
14420        } catch (err) {
14421          _didIteratorError26 = true;
14422          _iteratorError26 = err;
14423        } finally {
14424          try {
14425            if (!_iteratorNormalCompletion26 && _iterator26["return"] != null) {
14426              _iterator26["return"]();
14427            }
14428          } finally {
14429            if (_didIteratorError26) {
14430              throw _iteratorError26;
14431            }
14432          }
14433        }
14434      } else {
14435        // Called when the file finished uploading
14436        var allFilesFinished = true;
14437        progress = 100;
14438        var _iteratorNormalCompletion27 = true;
14439        var _didIteratorError27 = false;
14440        var _iteratorError27 = undefined;
14441
14442        try {
14443          for (var _iterator27 = files[Symbol.iterator](), _step27; !(_iteratorNormalCompletion27 = (_step27 = _iterator27.next()).done); _iteratorNormalCompletion27 = true) {
14444            var _file4 = _step27.value;
14445
14446            if (_file4.upload.progress !== 100 || _file4.upload.bytesSent !== _file4.upload.total) {
14447              allFilesFinished = false;
14448            }
14449
14450            _file4.upload.progress = progress;
14451            _file4.upload.bytesSent = _file4.upload.total;
14452          } // Nothing to do, all files already at 100%
14453
14454        } catch (err) {
14455          _didIteratorError27 = true;
14456          _iteratorError27 = err;
14457        } finally {
14458          try {
14459            if (!_iteratorNormalCompletion27 && _iterator27["return"] != null) {
14460              _iterator27["return"]();
14461            }
14462          } finally {
14463            if (_didIteratorError27) {
14464              throw _iteratorError27;
14465            }
14466          }
14467        }
14468
14469        if (allFilesFinished) {
14470          return;
14471        }
14472
14473        var _iteratorNormalCompletion28 = true;
14474        var _didIteratorError28 = false;
14475        var _iteratorError28 = undefined;
14476
14477        try {
14478          for (var _iterator28 = files[Symbol.iterator](), _step28; !(_iteratorNormalCompletion28 = (_step28 = _iterator28.next()).done); _iteratorNormalCompletion28 = true) {
14479            var _file5 = _step28.value;
14480            this.emit("uploadprogress", _file5, progress, _file5.upload.bytesSent);
14481          }
14482        } catch (err) {
14483          _didIteratorError28 = true;
14484          _iteratorError28 = err;
14485        } finally {
14486          try {
14487            if (!_iteratorNormalCompletion28 && _iterator28["return"] != null) {
14488              _iterator28["return"]();
14489            }
14490          } finally {
14491            if (_didIteratorError28) {
14492              throw _iteratorError28;
14493            }
14494          }
14495        }
14496      }
14497    }
14498  }, {
14499    key: "_finishedUploading",
14500    value: function _finishedUploading(files, xhr, e) {
14501      var response;
14502
14503      if (files[0].status === Dropzone.CANCELED) {
14504        return;
14505      }
14506
14507      if (xhr.readyState !== 4) {
14508        return;
14509      }
14510
14511      if (xhr.responseType !== 'arraybuffer' && xhr.responseType !== 'blob') {
14512        response = xhr.responseText;
14513
14514        if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) {
14515          try {
14516            response = JSON.parse(response);
14517          } catch (error) {
14518            e = error;
14519            response = "Invalid JSON response from server.";
14520          }
14521        }
14522      }
14523
14524      this._updateFilesUploadProgress(files);
14525
14526      if (!(200 <= xhr.status && xhr.status < 300)) {
14527        this._handleUploadError(files, xhr, response);
14528      } else {
14529        if (files[0].upload.chunked) {
14530          files[0].upload.finishedChunkUpload(this._getChunk(files[0], xhr));
14531        } else {
14532          this._finished(files, response, e);
14533        }
14534      }
14535    }
14536  }, {
14537    key: "_handleUploadError",
14538    value: function _handleUploadError(files, xhr, response) {
14539      if (files[0].status === Dropzone.CANCELED) {
14540        return;
14541      }
14542
14543      if (files[0].upload.chunked && this.options.retryChunks) {
14544        var chunk = this._getChunk(files[0], xhr);
14545
14546        if (chunk.retries++ < this.options.retryChunksLimit) {
14547          this._uploadData(files, [chunk.dataBlock]);
14548
14549          return;
14550        } else {
14551          console.warn('Retried this chunk too often. Giving up.');
14552        }
14553      }
14554
14555      this._errorProcessing(files, response || this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr);
14556    }
14557  }, {
14558    key: "submitRequest",
14559    value: function submitRequest(xhr, formData, files) {
14560      xhr.send(formData);
14561    }
14561 // Called internally when processing is finished.
14562    // Individual callbacks have to be called in the appropriate sections.
14563
14564  }, {
14565    key: "_finished",
14566    value: function _finished(files, responseText, e) {
14567      var _iteratorNormalCompletion29 = true;
14568      var _didIteratorError29 = false;
14569      var _iteratorError29 = undefined;
14570
14571      try {
14572        for (var _iterator29 = files[Symbol.iterator](), _step29; !(_iteratorNormalCompletion29 = (_step29 = _iterator29.next()).done); _iteratorNormalCompletion29 = true) {
14573          var file = _step29.value;
14574          file.status = Dropzone.SUCCESS;
14575          this.emit("success", file, responseText, e);
14576          this.emit("complete", file);
14577        }
14578      } catch (err) {
14579        _didIteratorError29 = true;
14580        _iteratorError29 = err;
14581      } finally {
14582        try {
14583          if (!_iteratorNormalCompletion29 && _iterator29["return"] != null) {
14584            _iterator29["return"]();
14585          }
14586        } finally {
14587          if (_didIteratorError29) {
14588            throw _iteratorError29;
14589          }
14590        }
14591      }
14592
14593      if (this.options.uploadMultiple) {
14594        this.emit("successmultiple", files, responseText, e);
14595        this.emit("completemultiple", files);
14596      }
14597
14598      if (this.options.autoProcessQueue) {
14599        return this.processQueue();
14600      }
14601    } // Called internally when processing is finished.
14602    // Individual callbacks have to be called in the appropriate sections.
14603
14604  }, {
14605    key: "_errorProcessing",
14606    value: function _errorProcessing(files, message, xhr) {
14607      var _iteratorNormalCompletion30 = true;
14608      var _didIteratorError30 = false;
14609      var _iteratorError30 = undefined;
14610
14611      try {
14612        for (var _iterator30 = files[Symbol.iterator](), _step30; !(_iteratorNormalCompletion30 = (_step30 = _iterator30.next()).done); _iteratorNormalCompletion30 = true) {
14613          var file = _step30.value;
14614          file.status = Dropzone.ERROR;
14615          this.emit("error", file, message, xhr);
14616          this.emit("complete", file);
14617        }
14618      } catch (err) {
14619        _didIteratorError30 = true;
14620        _iteratorError30 = err;
14621      } finally {
14622        try {
14623          if (!_iteratorNormalCompletion30 && _iterator30["return"] != null) {
14624            _iterator30["return"]();
14625          }
14626        } finally {
14627          if (_didIteratorError30) {
14628            throw _iteratorError30;
14629          }
14630        }
14631      }
14632
14633      if (this.options.uploadMultiple) {
14634        this.emit("errormultiple", files, message, xhr);
14635        this.emit("completemultiple", files);
14636      }
14637
14638      if (this.options.autoProcessQueue) {
14639        return this.processQueue();
14640      }
14641    }
14642  }], [{
14643    key: "uuidv4",
14644    value: function uuidv4() {
14645      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
14646        var r = Math.random() * 16 | 0,
14647            v = c === 'x' ? r : r & 0x3 | 0x8;
14648        return v.toString(16);
14649      });
14650    }
14651  }]);
14652
14653  return Dropzone;
14654}(Emitter);
14655
14656Dropzone.initClass();
14657Dropzone.version = "5.7.1"; // This is a map of options for your different dropzones. Add configurations
14658// to this object for your different dropzone elemens.
14659//
14660// Example:
14661//
14662//     Dropzone.options.myDropzoneElementId = { maxFilesize: 1 };
14663//
14664// To disable autoDiscover for a specific element, you can set `false` as an option:
14665//
14666//     Dropzone.options.myDisabledElementId = false;
14667//
14668// And in html:
14669//
14670//     <form action="/upload" id="my-dropzone-element-id" class="dropzone"></form>
14671
14672Dropzone.options = {}; // Returns the options for an element or undefined if none available.
14673
14674Dropzone.optionsForElement = function (element) {
14675  // Get the `Dropzone.options.elementId` for this element if it exists
14676  if (element.getAttribute("id")) {
14677    return Dropzone.options[camelize(element.getAttribute("id"))];
14678  } else {
14679    return undefined;
14680  }
14681}; // Holds a list of all dropzone instances
14682
14683
14684Dropzone.instances = []; // Returns the dropzone for given element if any
14685
14686Dropzone.forElement = function (element) {
14687  if (typeof element === "string") {
14688    element = document.querySelector(element);
14689  }
14690
14691  if ((element != null ? element.dropzone : undefined) == null) {
14692    throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");
14693  }
14694
14695  return element.dropzone;
14696}; // Set to false if you don't want Dropzone to automatically find and attach to .dropzone elements.
14697
14698
14699Dropzone.autoDiscover = true; // Looks for all .dropzone elements and creates a dropzone for them
14700
14701Dropzone.discover = function () {
14702  var dropzones;
14703
14704  if (document.querySelectorAll) {
14705    dropzones = document.querySelectorAll(".dropzone");
14706  } else {
14707    dropzones = []; // IE :(
14708
14709    var checkElements = function checkElements(elements) {
14710      return function () {
14711        var result = [];
14712        var _iteratorNormalCompletion31 = true;
14713        var _didIteratorError31 = false;
14714        var _iteratorError31 = undefined;
14715
14716        try {
14717          for (var _iterator31 = elements[Symbol.iterator](), _step31; !(_iteratorNormalCompletion31 = (_step31 = _iterator31.next()).done); _iteratorNormalCompletion31 = true) {
14718            var el = _step31.value;
14719
14720            if (/(^| )dropzone($| )/.test(el.className)) {
14721              result.push(dropzones.push(el));
14722            } else {
14723              result.push(undefined);
14724            }
14725          }
14726        } catch (err) {
14727          _didIteratorError31 = true;
14728          _iteratorError31 = err;
14729        } finally {
14730          try {
14731            if (!_iteratorNormalCompletion31 && _iterator31["return"] != null) {
14732              _iterator31["return"]();
14733            }
14734          } finally {
14735            if (_didIteratorError31) {
14736              throw _iteratorError31;
14737            }
14738          }
14739        }
14740
14741        return result;
14742      }();
14743    };
14744
14745    checkElements(document.getElementsByTagName("div"));
14746    checkElements(document.getElementsByTagName("form"));
14747  }
14748
14749  return function () {
14750    var result = [];
14751    var _iteratorNormalCompletion32 = true;
14752    var _didIteratorError32 = false;
14753    var _iteratorError32 = undefined;
14754
14755    try {
14756      for (var _iterator32 = dropzones[Symbol.iterator](), _step32; !(_iteratorNormalCompletion32 = (_step32 = _iterator32.next()).done); _iteratorNormalCompletion32 = true) {
14757        var dropzone = _step32.value;
14758
14759        // Create a dropzone unless auto discover has been disabled for specific element
14760        if (Dropzone.optionsForElement(dropzone) !== false) {
14761          result.push(new Dropzone(dropzone));
14762        } else {
14763          result.push(undefined);
14764        }
14765      }
14766    } catch (err) {
14767      _didIteratorError32 = true;
14768      _iteratorError32 = err;
14769    } finally {
14770      try {
14771        if (!_iteratorNormalCompletion32 && _iterator32["return"] != null) {
14772          _iterator32["return"]();
14773        }
14774      } finally {
14775        if (_didIteratorError32) {
14776          throw _iteratorError32;
14777        }
14778      }
14779    }
14780
14781    return result;
14782  }();
14783}; // Since the whole Drag'n'Drop API is pretty new, some browsers implement it,
14784// but not correctly.
14785// So I created a blacklist of userAgents. Yes, yes. Browser sniffing, I know.
14786// But what to do when browsers *theoretically* support an API, but crash
14787// when using it.
14788//
14789// This is a list of regular expressions tested against navigator.userAgent
14790//
14791// ** It should only be used on browser that *do* support the API, but
14792// incorrectly **
14793//
14794
14795
14796Dropzone.blacklistedBrowsers = [// The mac os and windows phone version of opera 12 seems to have a problem with the File drag'n'drop API.
14797/opera.*(Macintosh|Windows Phone).*version\/12/i]; // Checks if the browser is supported
14798
14799Dropzone.isBrowserSupported = function () {
14800  var capableBrowser = true;
14801
14802  if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) {
14803    if (!("classList" in document.createElement("a"))) {
14804      capableBrowser = false;
14805    } else {
14806      // The browser supports the API, but may be blacklisted.
14807      var _iteratorNormalCompletion33 = true;
14808      var _didIteratorError33 = false;
14809      var _iteratorError33 = undefined;
14810
14811      try {
14812        for (var _iterator33 = Dropzone.blacklistedBrowsers[Symbol.iterator](), _step33; !(_iteratorNormalCompletion33 = (_step33 = _iterator33.next()).done); _iteratorNormalCompletion33 = true) {
14813          var regex = _step33.value;
14814
14815          if (regex.test(navigator.userAgent)) {
14816            capableBrowser = false;
14817            continue;
14818          }
14819        }
14820      } catch (err) {
14821        _didIteratorError33 = true;
14822        _iteratorError33 = err;
14823      } finally {
14824        try {
14825          if (!_iteratorNormalCompletion33 && _iterator33["return"] != null) {
14826            _iterator33["return"]();
14827          }
14828        } finally {
14829          if (_didIteratorError33) {
14830            throw _iteratorError33;
14831          }
14832        }
14833      }
14834    }
14835  } else {
14836    capableBrowser = false;
14837  }
14838
14839  return capableBrowser;
14840};
14841
14842Dropzone.dataURItoBlob = function (dataURI) {
14843  // convert base64 to raw binary data held in a string
14844  // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
14845  var byteString = atob(dataURI.split(',')[1]); // separate out the mime component
14846
14847  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to an ArrayBuffer
14848
14849  var ab = new ArrayBuffer(byteString.length);
14850  var ia = new Uint8Array(ab);
14851
14852  for (var i = 0, end = byteString.length, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
14853    ia[i] = byteString.charCodeAt(i);
14854  } // write the ArrayBuffer to a blob
14855
14856
14857  return new Blob([ab], {
14858    type: mimeString
14859  });
14860}; // Returns an array without the rejected item
14861
14862
14863var without = function without(list, rejectedItem) {
14864  return list.filter(function (item) {
14865    return item !== rejectedItem;
14866  }).map(function (item) {
14867    return item;
14868  });
14869}; // abc-def_ghi -> abcDefGhi
14870
14871
14872var camelize = function camelize(str) {
14873  return str.replace(/[\-_](\w)/g, function (match) {
14874    return match.charAt(1).toUpperCase();
14875  });
14876}; // Creates an element from string
14877
14878
14879Dropzone.createElement = function (string) {
14880  var div = document.createElement("div");
14881  div.innerHTML = string;
14882  return div.childNodes[0];
14883}; // Tests if given element is inside (or simply is) the container
14884
14885
14886Dropzone.elementInside = function (element, container) {
14887  if (element === container) {
14888    return true;
14889  } // Coffeescript doesn't support do/while loops
14890
14891
14892  while (element = element.parentNode) {
14893    if (element === container) {
14894      return true;
14895    }
14896  }
14897
14898  return false;
14899};
14900
14901Dropzone.getElement = function (el, name) {
14902  var element;
14903
14904  if (typeof el === "string") {
14905    element = document.querySelector(el);
14906  }
14906 else if (el.nodeType != null) {
14907    element = el;
14908  }
14909
14910  if (element == null) {
14911    throw new Error("Invalid `".concat(name, "` option provided. Please provide a CSS selector or a plain HTML element."));
14912  }
14913
14914  return element;
14915};
14916
14917Dropzone.getElements = function (els, name) {
14918  var el, elements;
14919
14920  if (els instanceof Array) {
14921    elements = [];
14922
14923    try {
14924      var _iteratorNormalCompletion34 = true;
14925      var _didIteratorError34 = false;
14926      var _iteratorError34 = undefined;
14927
14928      try {
14929        for (var _iterator34 = els[Symbol.iterator](), _step34; !(_iteratorNormalCompletion34 = (_step34 = _iterator34.next()).done); _iteratorNormalCompletion34 = true) {
14930          el = _step34.value;
14931          elements.push(this.getElement(el, name));
14932        }
14933      } catch (err) {
14934        _didIteratorError34 = true;
14935        _iteratorError34 = err;
14936      } finally {
14937        try {
14938          if (!_iteratorNormalCompletion34 && _iterator34["return"] != null) {
14939            _iterator34["return"]();
14940          }
14941        } finally {
14942          if (_didIteratorError34) {
14943            throw _iteratorError34;
14944          }
14945        }
14946      }
14947    } catch (e) {
14948      elements = null;
14949    }
14950  } else if (typeof els === "string") {
14951    elements = [];
14952    var _iteratorNormalCompletion35 = true;
14953    var _didIteratorError35 = false;
14954    var _iteratorError35 = undefined;
14955
14956    try {
14957      for (var _iterator35 = document.querySelectorAll(els)[Symbol.iterator](), _step35; !(_iteratorNormalCompletion35 = (_step35 = _iterator35.next()).done); _iteratorNormalCompletion35 = true) {
14958        el = _step35.value;
14959        elements.push(el);
14960      }
14961    } catch (err) {
14962      _didIteratorError35 = true;
14963      _iteratorError35 = err;
14964    } finally {
14965      try {
14966        if (!_iteratorNormalCompletion35 && _iterator35["return"] != null) {
14967          _iterator35["return"]();
14968        }
14969      } finally {
14970        if (_didIteratorError35) {
14971          throw _iteratorError35;
14972        }
14973      }
14974    }
14975  } else if (els.nodeType != null) {
14976    elements = [els];
14977  }
14978
14979  if (elements == null || !elements.length) {
14980    throw new Error("Invalid `".concat(name, "` option provided. Please provide a CSS selector, a plain HTML element or a list of those."));
14981  }
14982
14983  return elements;
14984}; // Asks the user the question and calls accepted or rejected accordingly
14985//
14986// The default implementation just uses `window.confirm` and then calls the
14987// appropriate callback.
14988
14989
14990Dropzone.confirm = function (question, accepted, rejected) {
14991  if (window.confirm(question)) {
14992    return accepted();
14993  } else if (rejected != null) {
14994    return rejected();
14995  }
14996}; // Validates the mime type like this:
14997//
14998// https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept
14999
15000
15001Dropzone.isValidFile = function (file, acceptedFiles) {
15002  if (!acceptedFiles) {
15003    return true;
15004  } // If there are no accepted mime types, it's OK
15005
15006
15007  acceptedFiles = acceptedFiles.split(",");
15008  var mimeType = file.type;
15009  var baseMimeType = mimeType.replace(/\/.*$/, "");
15010  var _iteratorNormalCompletion36 = true;
15011  var _didIteratorError36 = false;
15012  var _iteratorError36 = undefined;
15013
15014  try {
15015    for (var _iterator36 = acceptedFiles[Symbol.iterator](), _step36; !(_iteratorNormalCompletion36 = (_step36 = _iterator36.next()).done); _iteratorNormalCompletion36 = true) {
15016      var validType = _step36.value;
15017      validType = validType.trim();
15018
15019      if (validType.charAt(0) === ".") {
15020        if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) {
15021          return true;
15022        }
15023      } else if (/\/\*$/.test(validType)) {
15024        // This is something like a image/* mime type
15025        if (baseMimeType === validType.replace(/\/.*$/, "")) {
15026          return true;
15027        }
15028      } else {
15029        if (mimeType === validType) {
15030          return true;
15031        }
15032      }
15033    }
15034  } catch (err) {
15035    _didIteratorError36 = true;
15036    _iteratorError36 = err;
15037  } finally {
15038    try {
15039      if (!_iteratorNormalCompletion36 && _iterator36["return"] != null) {
15040        _iterator36["return"]();
15041      }
15042    } finally {
15043      if (_didIteratorError36) {
15044        throw _iteratorError36;
15045      }
15046    }
15047  }
15048
15049  return false;
15050}; // Augment jQuery
15051
15052
15053if (typeof jQuery !== 'undefined' && jQuery !== null) {
15054  jQuery.fn.dropzone = function (options) {
15055    return this.each(function () {
15056      return new Dropzone(this, options);
15057    });
15058  };
15059}
15060
15061if (typeof module !== 'undefined' && module !== null) {
15062  module.exports = Dropzone;
15063} else {
15064  window.Dropzone = Dropzone;
15065} // Dropzone file status codes
15066
15067
15068Dropzone.ADDED = "added";
15069Dropzone.QUEUED = "queued"; // For backwards compatibility. Now, if a file is accepted, it's either queued
15070// or uploading.
15071
15072Dropzone.ACCEPTED = Dropzone.QUEUED;
15073Dropzone.UPLOADING = "uploading";
15074Dropzone.PROCESSING = Dropzone.UPLOADING; // alias
15075
15076Dropzone.CANCELED = "canceled";
15077Dropzone.ERROR = "error";
15078Dropzone.SUCCESS = "success";
15079/*
15080
15081 Bugfix for iOS 6 and 7
15082 Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
15083 based on the work of https://github.com/stomita/ios-imagefile-megapixel
15084
15085 */
15086// Detecting vertical squash in loaded image.
15087// Fixes a bug which squash image vertically while drawing into canvas for some images.
15088// This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel
15089
15090var detectVerticalSquash = function detectVerticalSquash(img) {
15091  var iw = img.naturalWidth;
15092  var ih = img.naturalHeight;
15093  var canvas = document.createElement("canvas");
15094  canvas.width = 1;
15095  canvas.height = ih;
15096  var ctx = canvas.getContext("2d");
15097  ctx.drawImage(img, 0, 0);
15098
15099  var _ctx$getImageData = ctx.getImageData(1, 0, 1, ih),
15100      data = _ctx$getImageData.data; // search image edge pixel position in case it is squashed vertically.
15101
15102
15103  var sy = 0;
15104  var ey = ih;
15105  var py = ih;
15106
15107  while (py > sy) {
15108    var alpha = data[(py - 1) * 4 + 3];
15109
15110    if (alpha === 0) {
15111      ey = py;
15112    } else {
15113      sy = py;
15114    }
15115
15116    py = ey + sy >> 1;
15117  }
15118
15119  var ratio = py / ih;
15120
15121  if (ratio === 0) {
15122    return 1;
15123  } else {
15124    return ratio;
15125  }
15126}; // A replacement for context.drawImage
15127// (args are for source and destination).
15128
15129
15130var drawImageIOSFix = function drawImageIOSFix(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
15131  var vertSquashRatio = detectVerticalSquash(img);
15132  return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
15133}; // Based on MinifyJpeg
15134// Source: http://www.perry.cz/files/ExifRestorer.js
15135// http://elicon.blog57.fc2.com/blog-entry-206.html
15136
15137
15138var ExifRestore =
15139/*#__PURE__*/
15140function () {
15141  function ExifRestore() {
15142    _classCallCheck(this, ExifRestore);
15143  }
15144
15145  _createClass(ExifRestore, null, [{
15146    key: "initClass",
15147    value: function initClass() {
15148      this.KEY_STR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
vendor: 5,099 bytes, lines 15148-15341
15148+/=';
15149    }
15150  }, {
15151    key: "encode64",
15152    value: function encode64(input) {
15153      var output = '';
15154      var chr1 = undefined;
15155      var chr2 = undefined;
15156      var chr3 = '';
15157      var enc1 = undefined;
15158      var enc2 = undefined;
15159      var enc3 = undefined;
15160      var enc4 = '';
15161      var i = 0;
15162
15163      while (true) {
15164        chr1 = input[i++];
15165        chr2 = input[i++];
15166        chr3 = input[i++];
15167        enc1 = chr1 >> 2;
15168        enc2 = (chr1 & 3) << 4 | chr2 >> 4;
15169        enc3 = (chr2 & 15) << 2 | chr3 >> 6;
15170        enc4 = chr3 & 63;
15171
15172        if (isNaN(chr2)) {
15173          enc3 = enc4 = 64;
15174        } else if (isNaN(chr3)) {
15175          enc4 = 64;
15176        }
15177
15178        output = output + this.KEY_STR.charAt(enc1) + this.KEY_STR.charAt(enc2) + this.KEY_STR.charAt(enc3) + this.KEY_STR.charAt(enc4);
15179        chr1 = chr2 = chr3 = '';
15180        enc1 = enc2 = enc3 = enc4 = '';
15181
15182        if (!(i < input.length)) {
15183          break;
15184        }
15185      }
15186
15187      return output;
15188    }
15189  }, {
15190    key: "restore",
15191    value: function restore(origFileBase64, resizedFileBase64) {
15192      if (!origFileBase64.match('data:image/jpeg;base64,')) {
15193        return resizedFileBase64;
15194      }
15195
15196      var rawImage = this.decode64(origFileBase64.replace('data:image/jpeg;base64,', ''));
15197      var segments = this.slice2Segments(rawImage);
15198      var image = this.exifManipulation(resizedFileBase64, segments);
15199      return "data:image/jpeg;base64,".concat(this.encode64(image));
15200    }
15201  }, {
15202    key: "exifManipulation",
15203    value: function exifManipulation(resizedFileBase64, segments) {
15204      var exifArray = this.getExifArray(segments);
15205      var newImageArray = this.insertExif(resizedFileBase64, exifArray);
15206      var aBuffer = new Uint8Array(newImageArray);
15207      return aBuffer;
15208    }
15209  }, {
15210    key: "getExifArray",
15211    value: function getExifArray(segments) {
15212      var seg = undefined;
15213      var x = 0;
15214
15215      while (x < segments.length) {
15216        seg = segments[x];
15217
15218        if (seg[0] === 255 & seg[1] === 225) {
15219          return seg;
15220        }
15221
15222        x++;
15223      }
15224
15225      return [];
15226    }
15227  }, {
15228    key: "insertExif",
15229    value: function insertExif(resizedFileBase64, exifArray) {
15230      var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', '');
15231      var buf = this.decode64(imageData);
15232      var separatePoint = buf.indexOf(255, 3);
15233      var mae = buf.slice(0, separatePoint);
15234      var ato = buf.slice(separatePoint);
15235      var array = mae;
15236      array = array.concat(exifArray);
15237      array = array.concat(ato);
15238      return array;
15239    }
15240  }, {
15241    key: "slice2Segments",
15242    value: function slice2Segments(rawImageArray) {
15243      var head = 0;
15244      var segments = [];
15245
15246      while (true) {
15247        var length;
15248
15249        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) {
15250          break;
15251        }
15252
15253        if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) {
15254          head += 2;
15255        } else {
15256          length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3];
15257          var endPoint = head + length + 2;
15258          var seg = rawImageArray.slice(head, endPoint);
15259          segments.push(seg);
15260          head = endPoint;
15261        }
15262
15263        if (head > rawImageArray.length) {
15264          break;
15265        }
15266      }
15267
15268      return segments;
15269    }
15270  }, {
15271    key: "decode64",
15272    value: function decode64(input) {
15273      var output = '';
15274      var chr1 = undefined;
15275      var chr2 = undefined;
15276      var chr3 = '';
15277      var enc1 = undefined;
15278      var enc2 = undefined;
15279      var enc3 = undefined;
15280      var enc4 = '';
15281      var i = 0;
15282      var buf = []; // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
15283
15284      var base64test = /[^A-Za-z0-9\+\/\=]/g;
15285
15286      if (base64test.exec(input)) {
15287        console.warn('There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, \'+\', \'/\',and \'=\'\nExpect errors in decoding.');
15288      }
15289
15290      input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
15291
15292      while (true) {
15293        enc1 = this.KEY_STR.indexOf(input.charAt(i++));
15294        enc2 = this.KEY_STR.indexOf(input.charAt(i++));
15295        enc3 = this.KEY_STR.indexOf(input.charAt(i++));
15296        enc4 = this.KEY_STR.indexOf(input.charAt(i++));
15297        chr1 = enc1 << 2 | enc2 >> 4;
15298        chr2 = (enc2 & 15) << 4 | enc3 >> 2;
15299        chr3 = (enc3 & 3) << 6 | enc4;
15300        buf.push(chr1);
15301
15302        if (enc3 !== 64) {
15303          buf.push(chr2);
15304        }
15305
15306        if (enc4 !== 64) {
15307          buf.push(chr3);
15308        }
15309
15310        chr1 = chr2 = chr3 = '';
15311        enc1 = enc2 = enc3 = enc4 = '';
15312
15313        if (!(i < input.length)) {
15314          break;
15315        }
15316      }
15317
15318      return buf;
15319    }
15320  }]);
15321
15322  return ExifRestore;
15323}();
15324
15325ExifRestore.initClass();
15326/*
15327 * contentloaded.js
15328 *
15329 * Author: Diego Perini (diego.perini at gmail.com)
15330 * Summary: cross-browser wrapper for DOMContentLoaded
15331 * Updated: 20101020
15332 * License: MIT
15333 * Version: 1.2
15334 *
15335 * URL:
15336 * http://javascript.nwbox.com/ContentLoaded/
15337 * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
15338 */
15339// @win window reference
15340// @fn function reference
15341
15342var contentLoaded = function contentLoaded(win, fn) {
15343  var done = false;
15344  var top = true;
15345  var doc = win.document;
15346  var root = doc.documentElement;
15347  var add = doc.addEventListener ? "addEventListener" : "attachEvent";
15348  var rem = doc.addEventListener ? "removeEventListener" : "detachEvent";
15349  var pre = doc.addEventListener ? "" : "on";
15350
15351  var init = function init(e) {
15352    if (e.type === "readystatechange" && doc.readyState !== "complete") {
15353      return;
15354    }
15355
15356    (e.type === "load" ? win : doc)[rem](pre + e.type, init, false);
15357
15358    if (!done && (done = true)) {
15359      return fn.call(win, e.type || e);
15360    }
15361  };
15362
15363  var poll = function poll() {
15364    try {
15365      root.doScroll("left");
15366    } catch (e) {
15367      setTimeout(poll, 50);
15368      return;
15369    }
15370
15371    return init("poll");
15372  };
15373
15374  if (doc.readyState !== "complete") {
15375    if (doc.createEventObject && root.doScroll) {
15376      try {
15377        top = !win.frameElement;
15378      } catch (error) {}
15379
15380      if (top) {
15381        poll();
15382      }
15383    }
15384
15385    doc[add](pre + "DOMContentLoaded", init, false);
15386    doc[add](pre + "readystatechange", init, false);
15387    return win[add](pre + "load", init, false);
15388  }
15389}; // As a single function to be able to write tests.
15390
15391
15392Dropzone._autoDiscoverFunction = function () {
15393  if (Dropzone.autoDiscover) {
15394    return Dropzone.discover();
15395  }
15396};
15397
15398contentLoaded(window, Dropzone._autoDiscoverFunction);
15399
15400function __guard__(value, transform) {
15401  return typeof value !== 'undefined' && value !== null ? transform(value) : undefined;
15402}
15403
15404function __guardMethod__(obj, methodName, transform) {
15405  if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') {
15406    return transform(obj, methodName);
15407  } else {
15408    return undefined;
15409  }
15410};
15411
15412function start_admin_promotion() {
15413
15414  $('div.admin.promoted').each(function(){
15415    $('input[type=checkbox]', $(this)).change(function(){
15416      if ($(this).is(':checked')) {
15417        $.ajax({
15418          type: 'POST',
15419          url: $(this).data('path')+'/promote',
15420          dataType: 'script'
15421        });
15422      } else {
15423        $.ajax({
15424          type: 'POST',
15425          url: $(this).data('path')+'/demote',
15426          dataType: 'script'
15427        });
15428      }
15429    })
15430  });
15431
15432};
15433function start_autocomplete($row) {
15434
15435  var $input_hidden = $('input.hidden', $row);
15436  var $input_search = $('input.search', $row);
15437  var $list_chosen = $('ol.chosen', $row);
15438  var $list_results = $('ol.results', $row);
15439  $list_chosen.hide();
15440  $list_results.hide();
15441  var $forgive_request;
15442  $input_search.keyup(function(){
15443    $forgive_request = window.setTimeout(function(){
15444      if ($input_search.val() == '') {
15445        $list_results.hide();
15446        $list_results.empty();
15447      } else {
15448        $list_results.show();
15449        if ($('li.rowclick', $row).length == 0) {
15450          $list_results.html('<li>Loading...</li>');
15451        }
15452        refresh_list_row($row);
15453      }
15454    }, 200);
15455  });
15456  $input_search.keydown(function(){
15457    window.clearTimeout($forgive_request);
15458  });
15459
15460}
15461
15462function refresh_list_row($row) {
15463
15464  var $div_field = $('div.field', $row);
15465  var $input_hidden = $('input.hidden', $row);
15466  var $input_search = $('input.search', $row);
15467  var $div_selection = $('div.selection', $row);
15468  var $list_chosen = $('ol.chosen', $row);
15469  var $list_results = $('ol.results', $row);
15470  $list_results.addClass('disable');
15471
15472  $.ajax({
15473    dataType: 'json',
15474    data: {
15475      scope: {active: 1},
15476      except: $row.data('except'),
15477      search: $input_search.val(),
15478      sort: {},
15479      start: 1,
15480      limit: 10
15481    },
15482    cache: false,
15483    url: $row.data('source'),
15484    success: $.proxy(function($data) {
15485
15486      $list_results.empty();
15487      $list_results.removeClass('disable');
15488      if ($data.length > 0) {
15489        $.each($data, $.proxy(function($key, $value) {
15490          if ($value['list_row']) {
15491            $list_row = $($value['list_row'])
15492            $list_row.click(function(){
15493              $input_hidden.val($('span.id', $(this)).text());
15494              $input_search.val('');
15495              $div_field.hide();
15496              $list_chosen.show();
15497              $list_chosen.html($('<li>').append($(this).html()));
15498              $cancel = $('<a class="cancel icon grey mdi mdi-close"></a>')
15499              $cancel.click(function(){
15500                $input_hidden.val('');
15501                $div_field.show();
15502                $list_chosen.hide();
15503                $list_chosen.empty();
15504                $(this).remove();
15505              });
15506              $div_selection.prepend($cancel);
15507              $list_results.hide();
15508              $list_results.empty();
15509            });
15510            $list_results.append($list_row)
15511          } else {
15512            console.log('list_row not found in index.json');
15513          }
15514
15515        }, this));
15516      } else {
15517        $list_results.append('<li>No matching items</li>');
15518      }
15519
15520    }, this)
15521  });
15522
15523}
15524
15525
15526function start_autocomplete_or_new($row) {
15527
15528  $input_search = $('input.search', $row);
15529  $div_selection = $('div.selection', $row);
15530  $list_results = $('ol.results', $row);
15531  $cancel = $('<a class="cancel icon grey mdi mdi-close"></a>')
15532  $cancel.click(function(){
15533    $div_selection.hide();
15534    $list_results.empty();
15535  });
15536  $div_selection.prepend($cancel);
15537  $div_selection.hide();
15538
15539  var $forgive_request;
15540  $input_search.keyup(function(){
15541    $forgive_request = window.setTimeout(function(){
15542      if ($input_search.val() == '') {
15543        $div_selection.hide();
15544        $list_results.empty();
15545      } else {
15546
15547        $.ajax({
15548          dataType: 'json',
15549          data: {
15550            scope: {active: 1},
15551            except: $row.data('except'),
15552            search: $input_search.val(),
15553            sort: {},
15554            start: 1,
15555            limit: 10
15556          },
15557          cache: false,
15558          url: $row.data('source'),
15559          success: $.proxy(function($data) {
15560
15561            if ($data.length > 0) {
15562              $div_selection.show();
15563              $list_results.empty();
15564              $.each($data, $.proxy(function($key, $value) {
15565                if ($value['name']) {
15566                  $list_name = $('<li class="rowclick">'+$value['name']+'</li>')
15567                  $list_name.click(function(){
15568                    $input_search.val($(this).text());
15569                    $div_selection.hide();
15570                    $list_results.empty();
15571                  });
15572                  $list_results.append($list_name)
15573                } else {
15574                  console.log('name not found in index.json');
15575                }
15576              }, this));
15577            } else {
15578              $div_selection.hide();
15579              $list_results.empty();
15580            }
15581
15582          }, this)
15583        });
15584      }
15585    }, 200);
15586  });
15587  $input_search.keydown(function(){
15588    window.clearTimeout($forgive_request);
15589  });
15590  $input_search.parents('form').focusin(function() {
15591    if (document.activeElement != $input_search) {
15592      $div_selection.hide();
15593      $list_results.empty();
15594      window.clearTimeout($forgive_request);
15595    }
15596  });
15597
15598};
15599(function(global, factory) {
15600  typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : factory(global.ActionCable = {});
15601})(this, function(exports) {
15602  "use strict";
15603  var adapters = {
15604    logger: self.console,
15605    WebSocket: self.WebSocket
15606  };
15607  var logger = {
15608    log: function log() {
15609      if (this.enabled) {
15610        var _adapters$logger;
15611        for (var _len = arguments.length, messages = Array(_len), _key = 0; _key < _len; _key++) {
15612          messages[_key] = arguments[_key];
15613        }
15614        messages.push(Date.now());
15615        (_adapters$logger = adapters.logger).log.apply(_adapters$logger, [ "[ActionCable]" ].concat(messages));
15616      }
15617    }
15618  };
15619  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) {
15620    return typeof obj;
15621  } : function(obj) {
15622    return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
15623  };
15624  var classCallCheck = function(instance, Constructor) {
15625    if (!(instance instanceof Constructor)) {
15626      throw new TypeError("Cannot call a class as a function");
15627    }
15628  };
15629  var createClass = function() {
15630    function defineProperties(target, props) {
15631      for (var i = 0; i < props.length; i++) {
15632        var descriptor = props[i];
15633        descriptor.enumerable = descriptor.enumerable || false;
15634        descriptor.configurable = true;
15635        if ("value" in descriptor) descriptor.writable = true;
15636        Object.defineProperty(target, descriptor.key, descriptor);
15637      }
15638    }
15639    return function(Constructor, protoProps, staticProps) {
15640      if (protoProps) defineProperties(Constructor.prototype, protoProps);
15641      if (staticProps) defineProperties(Constructor, staticProps);
15642      return Constructor;
15643    };
15644  }();
15645  var now = function now() {
15646    return new Date().getTime();
15647  };
15648  var secondsSince = function secondsSince(time) {
15649    return (now() - time) / 1e3;
15650  };
15651  var clamp = function clamp(number, min, max) {
15652    return Math.max(min, Math.min(max, number));
15653  };
15654  var ConnectionMonitor = function() {
15655    function ConnectionMonitor(connection) {
15656      classCallCheck(this, ConnectionMonitor);
15657      this.visibilityDidChange = this.visibilityDidChange.bind(this);
15658      this.connection = connection;
15659      this.reconnectAttempts = 0;
15660    }
15661    ConnectionMonitor.prototype.start = function start() {
15662      if (!this.isRunning()) {
15663        this.startedAt = now();
15664        delete this.stoppedAt;
15665        this.startPolling();
15666        addEventListener("visibilitychange", this.visibilityDidChange);
15667        logger.log("ConnectionMonitor started. pollInterval = " + this.getPollInterval() + " ms");
15668      }
15669    };
15670    ConnectionMonitor.prototype.stop = function stop() {
15671      if (this.isRunning()) {
15672        this.stoppedAt = now();
15673        this.stopPolling();
15674        removeEventListener("visibilitychange", this.visibilityDidChange);
15675        logger.log("ConnectionMonitor stopped");
15676      }
15677    };
15678    ConnectionMonitor.prototype.isRunning = function isRunning() {
15679      return this.startedAt && !this.stoppedAt;
15680    };
15681    ConnectionMonitor.prototype.recordPing = function recordPing() {
15682      this.pingedAt = now();
15683    };
15684    ConnectionMonitor.prototype.recordConnect = function recordConnect() {
15685      this.reconnectAttempts = 0;
15686      this.recordPing();
15687      delete this.disconnectedAt;
15688      logger.log("ConnectionMonitor recorded connect");
15689    };
15690    ConnectionMonitor.prototype.recordDisconnect = function recordDisconnect() {
15691      this.disconnectedAt = now();
15692      logger.log("ConnectionMonitor recorded disconnect");
15693    };
15694    ConnectionMonitor.prototype.startPolling = function startPolling() {
15695      this.stopPolling();
15696      this.poll();
15697    };
15698    ConnectionMonitor.prototype.stopPolling = function stopPolling() {
15699      clearTimeout(this.pollTimeout);
15700    };
15701    ConnectionMonitor.prototype.poll = function poll() {
15702      var _this = this;
15703      this.pollTimeout = setTimeout(function() {
15704        _this.reconnectIfStale();
15705        _this.poll();
15706      }, this.getPollInterval());
15707    };
15708    ConnectionMonitor.prototype.getPollInterval = function getPollInterval() {
15709      var _constructor$pollInte = this.constructor.pollInterval, min = _constructor$pollInte.min, max = _constructor$pollInte.max, multiplier = _constructor$pollInte.multiplier;
15710      var interval = multiplier * Math.log(this.reconnectAttempts + 1);
15711      return Math.round(clamp(interval, min, max) * 1e3);
15712    };
15713    ConnectionMonitor.prototype.reconnectIfStale = function reconnectIfStale() {
15714      if (this.connectionIsStale()) {
15715        logger.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + this.getPollInterval() + " ms, time disconnected = " + secondsSince(this.disconnectedAt) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
15716        this.reconnectAttempts++;
15717        if (this.disconnectedRecently()) {
15718          logger.log("ConnectionMonitor skipping reopening recent disconnect");
15719        } else {
15720          logger.log("ConnectionMonitor reopening");
15721          this.connection.reopen();
15722        }
15723      }
15724    };
15725    ConnectionMonitor.prototype.connectionIsStale = function connectionIsStale() {
15726      return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold;
15727    };
15728    ConnectionMonitor.prototype.disconnectedRecently = function disconnectedRecently() {
15729      return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
15730    };
15731    ConnectionMonitor.prototype.visibilityDidChange = function visibilityDidChange() {
15732      var _this2 = this;
15733      if (document.visibilityState === "visible") {
15734        setTimeout(function() {
15735          if (_this2.connectionIsStale() || !_this2.connection.isOpen()) {
15736            logger.log("ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = " + document.visibilityState);
15737            _this2.connection.reopen();
15738          }
15739        }, 200);
15740      }
15741    };
15742    return ConnectionMonitor;
15743  }();
15744  ConnectionMonitor.pollInterval = {
15745    min: 3,
15746    max: 30,
15747    multiplier: 5
15748  };
15749  ConnectionMonitor.staleThreshold = 6;
15750  var INTERNAL = {
15751    message_types: {
15752      welcome: "welcome",
15753      disconnect: "disconnect",
15754      ping: "ping",
15755      confirmation: "confirm_subscription",
15756      rejection: "reject_subscription"
15757    },
15758    disconnect_reasons: {
15759      unauthorized: "unauthorized",
15760      invalid_request: "invalid_request",
15761      server_restart: "server_restart"
15762    },
15763    default_mount_path: "/cable",
15764    protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
15765  };
15766  var message_types = INTERNAL.message_types, protocols = INTERNAL.protocols;
15767  var supportedProtocols = protocols.slice(0, protocols.length - 1);
15768  var indexOf = [].indexOf;
15769  var Connection = function() {
15770    function Connection(consumer) {
15771      classCallCheck(this, Connection);
15772      this.open = this.open.bind(this);
15773      this.consumer = consumer;
15774      this.subscriptions = this.consumer.subscriptions;
15775      this.monitor = new ConnectionMonitor(this);
15776      this.disconnected = true;
15777    }
15778    Connection.prototype.send = function send(data) {
15779      if (this.isOpen()) {
15780        this.webSocket.send(JSON.stringify(data));
15781        return true;
15782      } else {
15783        return false;
15784      }
15785    };
15786    Connection.prototype.open = function open() {
15787      if (this.isActive()) {
15788        logger.log("Attempted to open WebSocket, but existing socket is " + this.getState());
15789        return false;
15790      } else {
15791        logger.log("Opening WebSocket, current state is " + this.getState() + ", subprotocols: " + protocols);
15792        if (this.webSocket) {
15793          this.uninstallEventHandlers();
15794        }
15795        this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
15796        this.installEventHandlers();
15797        this.monitor.start();
15798        return true;
15799      }
15800    };
15801    Connection.prototype.close = function close() {
15802      var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
15803        allowReconnect: true
15804      }, allowReconnect = _ref.allowReconnect;
15805      if (!allowReconnect) {
15806        this.monitor.stop();
15807      }
15808      if (this.isActive()) {
15809        return this.webSocket.close();
15810      }
15811    };
15812    Connection.prototype.reopen = function reopen() {
15813      logger.log("Reopening WebSocket, current state is " + this.getState());
15814      if (this.isActive()) {
15815        try {
15816          return this.close();
15817        } catch (error) {
15818          logger.log("Failed to reopen WebSocket", error);
15819        } finally {
15820          logger.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
15821          setTimeout(this.open, this.constructor.reopenDelay);
15822        }
15823      } else {
15824        return this.open();
15825      }
15826    };
15827    Connection.prototype.getProtocol = function getProtocol() {
15828      if (this.webSocket) {
15829        return this.webSocket.protocol;
15830      }
15831    };
15832    Connection.prototype.isOpen = function isOpen() {
15833      return this.isState("open");
15834    };
15835    Connection.prototype.isActive = function isActive() {
15836      return this.isState("open", "connecting");
15837    };
15838    Connection.prototype.isProtocolSupported = function isProtocolSupported() {
15839      return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
15840    };
15841    Connection.prototype.isState = function isState() {
15842      for (var _len = arguments.length, states = Array(_len), _key = 0; _key < _len; _key++) {
15843        states[_key] = arguments[_key];
15844      }
15845      return indexOf.call(states, this.getState()) >= 0;
15846    };
15847    Connection.prototype.getState = function getState() {
15848      if (this.webSocket) {
15849        for (var state in adapters.WebSocket) {
15850          if (adapters.WebSocket[state] === this.webSocket.readyState) {
15851            return state.toLowerCase();
15852          }
15853        }
15854      }
15855      return null;
15856    };
15857    Connection.prototype.installEventHandlers = function installEventHandlers() {
15858      for (var eventName in this.events) {
15859        var handler = this.events[eventName].bind(this);
15860        this.webSocket["on" + eventName] = handler;
15861      }
15862    };
15863    Connection.prototype.uninstallEventHandlers = function uninstallEventHandlers() {
15864      for (var eventName in this.events) {
15865        this.webSocket["on" + eventName] = function() {};
15866      }
15867    };
15868    return Connection;
15869  }();
15870  Connection.reopenDelay = 500;
15871  Connection.prototype.events = {
15872    message: function message(event) {
15873      if (!this.isProtocolSupported()) {
15874        return;
15875      }
15876      var _JSON$parse = JSON.parse(event.data), identifier = _JSON$parse.identifier, message = _JSON$parse.message, reason = _JSON$parse.reason, reconnect = _JSON$parse.reconnect, type = _JSON$parse.type;
15877      switch (type) {
15878       case message_types.welcome:
15879        this.monitor.recordConnect();
15880        return this.subscriptions.reload();
15881
15882       case message_types.disconnect:
15883        logger.log("Disconnecting. Reason: " + reason);
15884        return this.close({
15885          allowReconnect: reconnect
15886        });
15887
15888       case message_types.ping:
15889        return this.monitor.recordPing();
15890
15891       case message_types.confirmation:
15892        this.subscriptions.confirmSubscription(identifier);
15893        return this.subscriptions.notify(identifier, "connected");
15894
15895       case message_types.rejection:
15896        return this.subscriptions.reject(identifier);
15897
15898       default:
15899        return this.subscriptions.notify(identifier, "received", message);
15900      }
15901    },
15902    open: function open() {
15903      logger.log("WebSocket onopen event, using '" + this.getProtocol() + "' subprotocol");
15904      this.disconnected = false;
15905      if (!this.isProtocolSupported()) {
15906        logger.log("Protocol is unsupported. Stopping monitor and disconnecting.");
15907        return this.close({
15908          allowReconnect: false
15909        });
15910      }
15911    },
15912    close: function close(event) {
15913      logger.log("WebSocket onclose event");
15914      if (this.disconnected) {
15915        return;
15916      }
15917      this.disconnected = true;
15918      this.monitor.recordDisconnect();
15919      return this.subscriptions.notifyAll("disconnected", {
15920        willAttemptReconnect: this.monitor.isRunning()
15921      });
15922    },
15923    error: function error() {
15924      logger.log("WebSocket onerror event");
15925    }
15926  };
15927  var extend = function extend(object, properties) {
15928    if (properties != null) {
15929      for (var key in properties) {
15930        var value = properties[key];
15931        object[key] = value;
15932      }
15933    }
15934    return object;
15935  };
15936  var Subscription = function() {
15937    function Subscription(consumer) {
15938      var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15939      var mixin = arguments[2];
15940      classCallCheck(this, Subscription);
15941      this.consumer = consumer;
15942      this.identifier = JSON.stringify(params);
15943      extend(this, mixin);
15944    }
15945    Subscription.prototype.perform = function perform(action) {
15946      var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15947      data.action = action;
15948      return this.send(data);
15949    };
15950    Subscription.prototype.send = function send(data) {
15951      return this.consumer.send({
15952        command: "message",
15953        identifier: this.identifier,
15954        data: JSON.stringify(data)
15955      });
15956    };
15957    Subscription.prototype.unsubscribe = function unsubscribe() {
15958      return this.consumer.subscriptions.remove(this);
15959    };
15960    return Subscription;
15961  }();
15962  var SubscriptionGuarantor = function() {
15963    function SubscriptionGuarantor(subscriptions) {
15964      classCallCheck(this, SubscriptionGuarantor);
15965      this.subscriptions = subscriptions;
15966      this.pendingSubscriptions = [];
15967    }
15968    SubscriptionGuarantor.prototype.guarantee = function guarantee(subscription) {
15969      if (this.pendingSubscriptions.indexOf(subscription) == -1) {
15970        logger.log("SubscriptionGuarantor guaranteeing " + subscription.identifier);
15971        this.pendingSubscriptions.push(subscription);
15972      } else {
15973        logger.log("SubscriptionGuarantor already guaranteeing " + subscription.identifier);
15974      }
15975      this.startGuaranteeing();
15976    };
15977    SubscriptionGuarantor.prototype.forget = function forget(subscription) {
15978      logger.log("SubscriptionGuarantor forgetting " + subscription.identifier);
15979      this.pendingSubscriptions = this.pendingSubscriptions.filter(function(s) {
15980        return s !== subscription;
15981      });
15982    };
15983    SubscriptionGuarantor.prototype.startGuaranteeing = function startGuaranteeing() {
15984      this.stopGuaranteeing();
15985      this.retrySubscribing();
15986    };
15987    SubscriptionGuarantor.prototype.stopGuaranteeing = function stopGuaranteeing() {
15988      clearTimeout(this.retryTimeout);
15989    };
15990    SubscriptionGuarantor.prototype.retrySubscribing = function retrySubscribing() {
15991      var _this = this;
15992      this.retryTimeout = setTimeout(function() {
15993        if (_this.subscriptions && typeof _this.subscriptions.subscribe === "function") {
15994          _this.pendingSubscriptions.map(function(subscription) {
15995            logger.log("SubscriptionGuarantor resubscribing " + subscription.identifier);
15996            _this.subscriptions.subscribe(subscription);
15997          });
15998        }
15999      }, 500);
16000    };
16001    return SubscriptionGuarantor;
16002  }();
16003  var Subscriptions = function() {
16004    function Subscriptions(consumer) {
16005      classCallCheck(this, Subscriptions);
16006      this.consumer = consumer;
16007      this.guarantor = new SubscriptionGuarantor(this);
16008      this.subscriptions = [];
16009    }
16010    Subscriptions.prototype.create = function create(channelName, mixin) {
16011      var channel = channelName;
16012      var params = (typeof channel === "undefined" ? "undefined" : _typeof(channel)) === "object" ? channel : {
16013        channel: channel
16014      };
16015      var subscription = new Subscription(this.consumer, params, mixin);
16016      return this.add(subscription);
16017    };
16018    Subscriptions.prototype.add = function add(subscription) {
16019      this.subscriptions.push(subscription);
16020      this.consumer.ensureActiveConnection();
16021      this.notify(subscription, "initialized");
16022      this.subscribe(subscription);
16023      return subscription;
16024    };
16025    Subscriptions.prototype.remove = function remove(subscription) {
16026      this.forget(subscription);
16027      if (!this.findAll(subscription.identifier).length) {
16028        this.sendCommand(subscription, "unsubscribe");
16029      }
16030      return subscription;
16031    };
16032    Subscriptions.prototype.reject = function reject(identifier) {
16033      var _this = this;
16034      return this.findAll(identifier).map(function(subscription) {
16035        _this.forget(subscription);
16036        _this.notify(subscription, "rejected");
16037        return subscription;
16038      });
16039    };
16040    Subscriptions.prototype.forget = function forget(subscription) {
16041      this.guarantor.forget(subscription);
16042      this.subscriptions = this.subscriptions.filter(function(s) {
16043        return s !== subscription;
16044      });
16045      return subscription;
16046    };
16047    Subscriptions.prototype.findAll = function findAll(identifier) {
16048      return this.subscriptions.filter(function(s) {
16049        return s.identifier === identifier;
16050      });
16051    };
16052    Subscriptions.prototype.reload = function reload() {
16053      var _this2 = this;
16054      return this.subscriptions.map(function(subscription) {
16055        return _this2.subscribe(subscription);
16056      });
16057    };
16058    Subscriptions.prototype.notifyAll = function notifyAll(callbackName) {
16059      var _this3 = this;
16060      for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
16061        args[_key - 1] = arguments[_key];
16062      }
16063      return this.subscriptions.map(function(subscription) {
16064        return _this3.notify.apply(_this3, [ subscription, callbackName ].concat(args));
16065      });
16066    };
16067    Subscriptions.prototype.notify = function notify(subscription, callbackName) {
16068      for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
16069        args[_key2 - 2] = arguments[_key2];
16070      }
16071      var subscriptions = void 0;
16072      if (typeof subscription === "string") {
16073        subscriptions = this.findAll(subscription);
16074      } else {
16075        subscriptions = [ subscription ];
16076      }
16077      return subscriptions.map(function(subscription) {
16078        return typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : undefined;
16079      });
16080    };
16081    Subscriptions.prototype.subscribe = function subscribe(subscription) {
16082      if (this.sendCommand(subscription, "subscribe")) {
16083        this.guarantor.guarantee(subscription);
16084      }
16085    };
16086    Subscriptions.prototype.confirmSubscription = function confirmSubscription(identifier) {
16087      var _this4 = this;
16088      logger.log("Subscription confirmed " + identifier);
16089      this.findAll(identifier).map(function(subscription) {
16090        return _this4.guarantor.forget(subscription);
16091      });
16092    };
16093    Subscriptions.prototype.sendCommand = function sendCommand(subscription, command) {
16094      var identifier = subscription.identifier;
16095      return this.consumer.send({
16096        command: command,
16097        identifier: identifier
16098      });
16099    };
16100    return Subscriptions;
16101  }();
16102  var Consumer = function() {
16103    function Consumer(url) {
16104      classCallCheck(this, Consumer);
16105      this._url = url;
16106      this.subscriptions = new Subscriptions(this);
16107      this.connection = new Connection(this);
16108    }
16109    Consumer.prototype.send = function send(data) {
16110      return this.connection.send(data);
16111    };
16112    Consumer.prototype.connect = function connect() {
16113      return this.connection.open();
16114    };
16115    Consumer.prototype.disconnect = function disconnect() {
16116      return this.connection.close({
16117        allowReconnect: false
16118      });
16119    };
16120    Consumer.prototype.ensureActiveConnection = function ensureActiveConnection() {
16121      if (!this.connection.isActive()) {
16122        return this.connection.open();
16123      }
16124    };
16125    createClass(Consumer, [ {
16126      key: "url",
16127      get: function get$$1() {
16128        return createWebSocketURL(this._url);
16129      }
16130    } ]);
16131    return Consumer;
16132  }();
16133  function createWebSocketURL(url) {
16134    if (typeof url === "function") {
16135      url = url();
16136    }
16137    if (url && !/^wss?:/i.test(url)) {
16138      var a = document.createElement("a");
16139      a.href = url;
16140      a.href = a.href;
16141      a.protocol = a.protocol.replace("http", "ws");
16142      return a.href;
16143    } else {
16144      return url;
16145    }
16146  }
16147  function createConsumer() {
16148    var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getConfig("url") || INTERNAL.default_mount_path;
16149    return new Consumer(url);
16150  }
16151  function getConfig(name) {
16152    var element = document.head.querySelector("meta[name='action-cable-" + name + "']");
16153    if (element) {
16154      return element.getAttribute("content");
16155    }
16156  }
16157  exports.Connection = Connection;
16158  exports.ConnectionMonitor = ConnectionMonitor;
16159  exports.Consumer = Consumer;
16160  exports.INTERNAL = INTERNAL;
16161  exports.Subscription = Subscription;
16162  exports.Subscriptions = Subscriptions;
16163  exports.SubscriptionGuarantor = SubscriptionGuarantor;
16164  exports.adapters = adapters;
16165  exports.createWebSocketURL = createWebSocketURL;
16166  exports.logger = logger;
16167  exports.createConsumer = createConsumer;
16168  exports.getConfig = getConfig;
16169  Object.defineProperty(exports, "__esModule", {
16170    value: true
16171  });
16172});
16173// Action Cable provides the framework to deal with WebSockets in Rails.
16174// You can generate new channels where WebSocket features live using the rails generate channel command.
16175//
16176
16177
16178
16179
16180(function() {
16181  this.App || (this.App = {});
16182
16183  App.cable = ActionCable.createConsumer();
16184
16185}).call(this);
16186
16187function start_categories() {
16188
16189  $('div.row.categories').each(function(){
16190
16191    var $categories = $('input[id$=all_categories]', this).val().split(', ');
16192    $('ul.categories li label', this).each(function(){
16193      if ($.inArray($(this).text(), $categories) >= 0) {
16194        $('input[type=checkbox]', $(this).parent('li')).prop('checked', true);
16195      }
16196    });
16197    $('ul.categories li input[type=checkbox]', this).change(function(){
16198      refresh_categories($(this).parents('div.row.categories'));
16199    });
16200    $('input#newcategory', this).bind('change keyup', function(){
16201      refresh_categories($(this).parents('div.row.categories'));
16202    });
16203    $('input[id$=all_categories]', this).hide();
16204
16205  });
16206
16207}
16208
16209function refresh_categories($row) {
16210
16211  var $categories = [];
16212  $('input[type=checkbox]:checked', $row).each(function(){
16213    $categories.push($('label', $(this).parent('li')).text());
16214  });
16215  var $newcategories = $('input#newcategory', $row).val().split(',')
16216  for (var $i = 0; $i < $newcategories.length; $i++) {
16217    if ($newcategories[$i].trim() != '') {
16218      $categories.push($newcategories[$i].trim());
16219    }
16220  }
16221  $('input[id$=all_categories]', $row).val($categories.join(', '));
16222
16223};
16224//Override the default confirm dialog by rails
16225$.rails.allowAction = function($element){
16226  if ($element.data("confirm") == undefined){
16227    return true;
16228  }
16229  $.rails.showConfirmationDialog($element);
16230  return false;
16231}
16232//User click confirm button
16233$.rails.confirmed = function($element){
16234  $element.data("confirm", null);
16235  $element.trigger("click.rails");
16236}
16237//Display the confirmation dialog
16238$.rails.showConfirmationDialog = function($element){
16239  var message = $element.data("confirm");
16240  $confirm = $('<div id="confirm"></div>');
16241  $confirm.append('<h2>Please confirm</h2>');
16242  $content = $('<div class="content"></div>');
16243  $content.append($('<p></p>').text(message));
16244  $content.append('<div class="actions"><a href="#" class="confirm button">Confirm</a> <a href="#" class="cancel secondary button">Cancel</a></div>');
16245  $confirm.append($content);
16246  start_modal($('<div>').append($confirm.clone()).html());
16247  $('div#confirm a.confirm').click(function($event){
16248    $.rails.confirmed($element);
16249    close_modal();
16250    $event.preventDefault();
16251  });
16252  $('div#confirm a.cancel').click(function($event){
16253    close_modal();
16254    $event.preventDefault();
16255  });
16256}
16257
16258function confirm_labels() {
16259  $('label').click(function(){
16260    if ($(this).data("confirm") == undefined){
16261      return true;
16262    }
16263    $.rails.showConfirmationDialog($(this));
16264    return false;
16265  });
16266};
16267function show_cookie_notice() {
16268  if (getParameterByName('cookie')) {
16269    $('div#cookie').show();
16270  }
16271  if (document.cookie.indexOf('cookie_notice=true') < 0) {
16272    $('div#cookie').show();
16273  }
16274}
16275
16276function hide_cookie_notice() {
16277  document.cookie = 'cookie_notice=true; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/';
16278  $('div#cookie div.holder').fadeOut(200,function(){
16279    $('div#cookie div.holder').css('display', 'block');
16280    $('div#cookie div.holder').css('visibility', 'hidden');
16281    $('div#cookie').slideUp(200);
16282  });
16283};
16284
16285function start_dates() {
16286
16287  if ($('div.row.datetime-start').length && $('div.row.datetime-end').length) {
16288
16289    $('div.row.datetime-start').each(function(){
16290      $end_date_row = $('div.row.datetime-end', $(this).parents('form'));
16291      $checkbox = $('<input type="checkbox" id="toggle-end-'+$(this).index()+'" />');
16292      if ($('input', $end_date_row).val() == '') {
16293        $end_date_row.hide();
16294      } else {
16295        $checkbox.prop('checked', true);
16296      }
16297      $label = $('<label for="toggle-end-'+$(this).index()+'">Add end date</label>');
16298      $('div.field', this).append($checkbox);
16299      $('div.field', this).append($label);
16300      $checkbox.change(function(){
16301        toggle_end_date($checkbox, $(this).parents('div.row.datetime-start'), $end_date_row);
16302      });
16303    });
16304
16305  }
16306
16307}
16308
16309function pad(n, width) {
16310  z = '0';
16311  n = n + '';
16312  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
16313}
16314
16315function toggle_end_date($checkbox, $start_date_row, $end_date_row) {
16316
16317  if ($checkbox.prop('checked')) {
16318    $('input', $end_date_row).val($('input', $start_date_row).val());
16319    $end_date_row.show();
16320  } else {
16321    $('input', $end_date_row).val('')
16322    $end_date_row.hide();
16323  }
16324
16325};
16326function start_delegates($delegates) {
16327
16328  refresh_delegates($delegates);
16329
16330  $('a.add', $delegates).click(function(e){
16331    add_delegate($delegates);
16332    e.preventDefault();
16333  });
16334
16335  $('a.remove', $delegates).click(function(e){
16336    remove_delegate($delegates);
16337    e.preventDefault();
16338  });
16339
16340}
16341
16342function refresh_delegates($delegates) {
16343
16344  $('section.delegate', $delegates).each(function(){
16345    if ($('input.delete', $(this)).prop('checked') == true) {
16346      $(this).hide();
16347    } else {
16348      $(this).show();
16349    }
16350  });
16351
16352
16353  if ($('section.delegate:visible', $delegates).length < $('section.delegate', $delegates).length) {
16354//    $('a.add', $delegates).show();
16355    $('a.add', $delegates).removeClass('disabled');
16356  } else {
16357//    $('a.add', $delegates).hide();
16358    $('a.add', $delegates).addClass('disabled');
16359  }
16360
16361  if ($('section.delegate:visible', $delegates).length > 1) {
16362//    $('a.remove', $delegates).show();
16363    $('a.remove', $delegates).removeClass('disabled');
16364  } else {
16365//    $('a.remove', $delegates).hide();
16366    $('a.remove', $delegates).addClass('disabled');
16367  }
16368
16369}
16370
16371function add_delegate($delegates) {
16372  $checkbox = $('input.delete:checked:first', $delegates)
16373  $checkbox.prop('checked', false);
16374  $delegate = $checkbox.parents('section.delegate');
16375  $delegate.slideDown(200,function(){
16376    refresh_delegates($delegates);
16377  });
16378}
16379
16380function remove_delegate($delegates) {
16381  $checkbox = $('input.delete:not(:checked):last', $delegates)
16382  $checkbox.prop('checked', true);
16383  $delegate = $checkbox.parents('section.delegate');
16384  $delegate.slideUp(200,function(){
16385    refresh_delegates($delegates);
16386  });
16387};
16388function start_fields() {
16389
16390  refresh_field_options($('select[id$="_input"]'));
16391  $('select[id$="_input"]').change(function(){
16392    refresh_field_options($(this));
16393  });
16394  refresh_option_deletes();
16395  attach_more_options_link();
16396  confirm_labels();
16397
16398}
16399
16400function refresh_field_options($select) {
16401
16402  $('div.optionsort').hide();
16403  if ($('option:selected', $select).attr('class') == 'options') {
16404    $('div.optionsort').show();
16405  }
16406
16407}
16408
16409function refresh_option_deletes() {
16410
16411  $('ol.options input.delete').change(function(){
16412    if ($(this).is(':checked')) {
16413      $(this).parents('li.option').hide();
16414    }
16415  });
16416
16417}
16418
16419function start_option_sort() {
16420
16421  $('ol.options').each(function(){
16422    refresh_option_sort($(this));
16423  });
16424
16425  $('div.optionsort').nestable({
16426    maxDepth: 1,
16427    rootClass: 'optionsort',
16428    listClass: 'options',
16429    itemClass: 'option',
16430    dragClass: 'dragging',
16431    handleClass: 'weight',
16432    placeClass: 'place'
16433  });
16434  $('div.optionsort').on('change', function() {
16435    refresh_option_sort($('ol.options'));
16436  });
16437
16438}
16439
16440function refresh_option_sort($sortlist) {
16441
16442  $('select[id$="weight"]', $sortlist).each(function(index){
16443    $(this).val($(this).parents('li').index()+1);
16444  });
16445
16446}
16447
16448function attach_more_options_link() {
16449
16450  $link_add = $('<a href="#" class="mdi mdi-plus">Add an option</a>');
16451  $link_add.click(function($event){
16452    $i = $('li.option', $(this).parents('div.optionsort')).length;
16453    $new_option = $('<li class="option"><div class="weight"><span class="mdi mdi-drag-vertical"></span><div class="row select weight"><div class="field"><select name="'+$(this).parents('div.optionsort').data('target')+'_field[field_options_attributes]['+$i+'][weight]" id="'+$(this).parents('div.optionsort').data('target')+'_field_field_options_attributes_'+$i+'_weight"><option selected="selected" value="0">0</option></select></div></div></div>&nbsp;<div class="label"><div class="row text option snug"><div class="field"><input size="50" type="text" name="'+$(this).parents('div.optionsort').data('target')+'_field[field_options_attributes]['+$i+'][option]" id="'+$(this).parents('div.optionsort').data('target')+'_field_field_options_attributes_'+$i+'_option"></div></div></div>&nbsp;<div class="actions"><a href="#" class="delete icon mdi mdi-delete"><span>Delete</span></a></div><div class="count"></div></li>');
16454    for ($i = 1; $i <= 1000; $i++) {
16455      $('div.weight select', $new_option).append('<option value="'+$i+'">'+$i+'</option>');
16456    }
16457    $('ol.options', $(this).parents('div.optionsort')).append($new_option);
16458    $('div#options ol.options a.delete').click(function($event){
16459      $(this).parents('li.option').remove();
16460      refresh_option_sort($('ol.options', $(this).parents('div.optionsort')));
16461      $event.preventDefault();
16462    });
16463    refresh_option_sort($('ol.options', $(this).parents('div.optionsort')));
16464    $event.preventDefault();
16465  });
16466  $('div#options div.add').append($link_add);
16467
16468};
16469function set_cursor($form) {
16470
16471  exceptions = [
16472    'new_submission',
16473    'new_basket',
16474    'edit_basket'
16475  ]
16476
16477  if ($.inArray($form.attr('class'), exceptions) == -1) {
16478    $inputs = $form.find(':input.error')
16479    if ($inputs.length != 0) {
16480      if ($($inputs[0]).parents('div.tab-frame').length > 0) {
16481        $anchor = $('a[href="#'+$($inputs[0]).parents('div.tab-frame').attr('id')+'"]')
16482        set_tab_for($anchor);
16483      }
16484      $inputs[0].focus();
16485    } else {
16486      $form.find(':input, div.redactor-in').filter(':visible:first').focus();
16487    }
16488  }
16489
16490}
16491
16492function attach_error($id, $field, $message) {
16493
16494  selector = '[id^="'+$id+'"]';
16495  $(selector).addClass('error');
16496  $errors = $('<div class="info errors"></div>');
16497  if ($field != '') {
16498    $errors.append('<p>'+$field+' '+$message+'</p>');
16499  } else {
16500    $errors.append('<p>'+$message+'</p>');
16501  }
16502  if ($('div.field', $($id)).length == 0) {
16503    $(selector).closest('div.field').append($errors);
16504  } else {
16505    $(selector).append($errors);
16506  }
16507
16508}
16509
16510function remove_errors($form) {
16511
16512  $('div.row', $form).removeClass('error');
16513  $('input', $form).removeClass('error');
16514  $('select', $form).removeClass('error');
16515  $('textarea', $form).removeClass('error');
16516  $('div.info.errors', $form).remove();
16517
16518}
16519
16520function start_forms() {
16521
16522  if ($('body.show').length == 0) {
16523    set_cursor($('div#content form:first'));
16524  }
16525
16526}
16527
16528function start_form_attachments() {
16529
16530  refresh_form_attachments();
16531
16532  $('div#form-attachments input.delete').each(function(){
16533    $(this).change(function(){
16534      if ($(this).is(':checked')) {
16535        $(this).parents('tr').hide();
16536      }
16537      refresh_form_attachments();
16538    });
16539    if ($(this).is(':checked')) {
16540      $(this).parents('tr').hide();
16541    }
16542  });
16543
16544  $('div#form-attachments a.add').click(function(event){
16545    $i = $('div#form-attachments table tr.attachment').length;
16546    $('div#form-attachments table').append('<tr class="attachment"><td class="field"><div class="row text description tight"><div class="field"><input size="30" placeholder="Description" type="text" name="'+$('body').data('model')+'[node_attributes][attachments_attributes]['+$i+'][description]" id="'+$('body').data('model')+'_node_attributes_attachments_attributes_'+$i+'_description"></div></div></td><td><div class="row file file tight"><div class="field"><input size="30" type="file" name="'+$('body').data('model')+'[node_attributes][attachments_attributes]['+$i+'][file]" id="'+$('body').data('model')+'_node_attributes_attachments_attributes_'+$i+'_file"><div class="info rules">The maximum file size is 10MB</div></div></div></td><td class="actions"><a href="#" class="delete icon mdi mdi-delete"><span>
16546Delete</span></a></td></tr>');
16547    $('div#form-attachments table a.delete').click(function($event){
16548      $(this).parents('tr.attachment').remove();
16549      refresh_form_attachments();
16550      $event.preventDefault();
16551    });
16552    refresh_form_attachments();
16553    event.preventDefault();
16554  });
16555
16556}
16557
16558function refresh_form_attachments() {
16559
16560  if ($('div#form-attachments table tr.attachment:visible').length > 0) {
16561    $('div#form-attachments table tr.none').remove();
16562  } else {
16563    $('div#form-attachments table').append('<tr class="none"><td colspan="3">There are no attachments at the moment.</td></tr>');
16564  }
16565
16566}
16567
16568function start_file_attachments() {
16569
16570  refresh_file_attachments();
16571
16572  $('div#file-attachments input.delete').each(function(){
16573    $(this).change(function(){
16574      if ($(this).is(':checked')) {
16575        $(this).parents('tr').hide();
16576      }
16577      refresh_file_attachments();
16578    });
16579    if ($(this).is(':checked')) {
16580      $(this).parents('tr').hide();
16581    }
16582  });
16583
16584  $('div#file-attachments a.add').click(function(event){
16585    $i = $('div#file-attachments table tr.attachment').length;
16586    $('div#file-attachments table').append('<tr class="attachment"><td class="field"><div class="row text description tight"><div class="field"><input size="30" placeholder="Description" type="text" name="'+$('body').data('model')+'[file_attachments_attributes]['+$i+'][description]" id="'+$('body').data('model')+'_file_attachments_attributes_'+$i+'_description"></div></div></td><td><div class="row file file tight"><div class="field"><input size="30" type="file" name="'+$('body').data('model')+'[file_attachments_attributes]['+$i+'][file]" id="'+$('body').data('model')+'_file_attachments_attributes_'+$i+'_file"><div class="info rules">The maximum file size is 10MB</div></div></div></td><td class="actions"><a href="#" class="delete icon mdi mdi-delete"><span>Delete</span></a></td></tr>');
16587    $('div#file-attachments table a.delete').click(function($event){
16588      $(this).parents('tr.attachment').remove();
16589      refresh_file_attachments();
16590      $event.preventDefault();
16591    });
16592    refresh_file_attachments();
16593    event.preventDefault();
16594  });
16595
16596}
16597
16598function refresh_file_attachments() {
16599
16600  if ($('div#file-attachments table tr.attachment:visible').length > 0) {
16601    $('div#file-attachments table tr.none').remove();
16602  } else {
16603    $('div#file-attachments table').append('<tr class="none"><td colspan="3">There are no attachments at the moment.</td></tr>');
16604  }
16605
16606}
16607
16608function start_image_scale($target) {
16609
16610  reset_image_scale($target);
16611
16612  $('input[name$="['+$target+'_scale]"]').change(function(){
16613    reset_image_scale($target);
16614  });
16615
16616}
16617
16618function reset_image_scale($target) {
16619
16620  $('div[id^="preview-'+$target+'_asset_id"]').hide();
16621  $('div.row.'+$target+'_scale').hide();
16622  $('div.row.'+$target+'_link').hide();
16623  $('div.row.'+$target+'_alt').hide();
16624
16625  if (parseInt($('input[id$="'+$target+'_asset_id"]').val()) > 0) {
16626    //$('div#preview-'+$target+'_asset_id').html('');
16627    $('div.row.'+$target+'_scale').show();
16628    $('div.row.'+$target+'_link').show();
16629    $('div.row.'+$target+'_alt').show();
16630    console.log();
16631    $.ajax({
16632      type: 'GET',
16633      dataType: 'json',
16634      url: '/'+$target+'_assets/'+$('input[id$="'+$target+'_asset_id"]').val(),
16635      success: function($data) {
16636        $('input[name$="['+$target+'_scale]"]').parents('div.radios').append($('a[id^="remove-'+$target+'_asset_id"]'));
16637        $('input[name$="['+$target+'_scale]"]').each(function(){
16638          $('label span.image', $(this).parents('div.option')).html('');
16639          switch($(this).val()) {
16640            case 'fill':
16641              $('label span.image', $(this).parents('div.option')).append($data.preview_tile_fill);
16642              break;
16643            case 'fit':
16644              $('label span.image', $(this).parents('div.option')).append($data.preview_tile_fit);
16645              break;
16646            case 'full':
16647              $('label span.image', $(this).parents('div.option')).append($data.preview_banner);
16648              break;
16649            case 'half':
16650              $('label span.image', $(this).parents('div.option')).append($data.preview_banner_half);
16651              break;
16652          }
16653        });
16654        switch($('input[name$="['+$target+'_scale]"]:checked').val()) {
16655          case 'fill':
16656            $('div[id^="preview-'+$target+'_asset_id"]').html($data.preview_tile_fill);
16657            break;
16658          case 'fit':
16659            $('div[id^="preview-'+$target+'_asset_id"]').html($data.preview_tile_fit);
16660            break;
16661          case 'full':
16662            $('div[id^="preview-'+$target+'_asset_id"]').html($data.preview_banner);
16663            break;
16664          case 'half':
16665            $('div[id^="preview-'+$target+'_asset_id"]').html($data.preview_banner_half);
16666            break;
16667        }
16668      }
16669    });
16670  }
16671
16672}
16673
16674function reset_image_position($target) {
16675
16676  $('div.row.'+$target+'_position').hide();
16677  $('div.row.'+$target+'_link').hide();
16678  $('div.row.'+$target+'_alt').hide();
16679
16680  if (parseInt($('input[id$="'+$target+'_asset_id"]').val()) > 0) {
16681    $('div.row.'+$target+'_position').show();
16682    $('div.row.'+$target+'_link').show();
16683    $('div.row.'+$target+'_alt').show();
16684  }
16685
16686}
16687
16688function start_show_password() {
16689  $('#show_password_toggle').on('change', function() {
16690    var $password = $('#login_password');
16691    if ($(this).is(':checked')) {
16692      $password.attr('type', 'text');
16693    } else {
16694      $password.attr('type', 'password');
16695    }
16696  });
16697  
16698};
16699function fetchInvoice(invoice_hash_id) {
16700  return $.ajax({
16701    type: 'GET',
16702    url: '/invoices/'+invoice_hash_id+'/intend',
16703    dataType: 'json'
16704  });
16705};
16706// This [jQuery](http://jquery.com/) plugin implements an `<iframe>`
16707// [transport](http://api.jquery.com/extending-ajax/#Transports) so that
16708// `$.ajax()` calls support the uploading of files using standard HTML file
16709// input fields. This is done by switching the exchange from `XMLHttpRequest`
16710// to a hidden `iframe` element containing a form that is submitted.
16711
16712// The [source for the plugin](http://github.com/cmlenz/jquery-iframe-transport)
16713// is available on [Github](http://github.com/) and dual licensed under the MIT
16714// or GPL Version 2 licenses.
16715
16716// ## Usage
16717
16718// To use this plugin, you simply add an `iframe` option with the value `true`
16719// to the Ajax settings an `$.ajax()` call, and specify the file fields to
16720// include in the submssion using the `files` option, which can be a selector,
16721// jQuery object, or a list of DOM elements containing one or more
16722// `<input type="file">` elements:
16723
16724//     $("#myform").submit(function() {
16725//         $.ajax(this.action, {
16726//             files: $(":file", this),
16727//             iframe: true
16728//         }).complete(function(data) {
16729//             console.log(data);
16730//         });
16731//     });
16732
16733// The plugin will construct hidden `<iframe>` and `<form>` elements, add the
16734// file field(s) to that form, submit the form, and process the response.
16735
16736// If you want to include other form fields in the form submission, include
16737// them in the `data` option, and set the `processData` option to `false`:
16738
16739//     $("#myform").submit(function() {
16740//         $.ajax(this.action, {
16741//             data: $(":text", this).serializeArray(),
16742//             files: $(":file", this),
16743//             iframe: true,
16744//             processData: false
16745//         }).complete(function(data) {
16746//             console.log(data);
16747//         });
16748//     });
16749
16750// ### Response Data Types
16751
16752// As the transport does not have access to the HTTP headers of the server
16753// response, it is not as simple to make use of the automatic content type
16754// detection provided by jQuery as with regular XHR. If you can't set the
16755// expected response data type (for example because it may vary depending on
16756// the outcome of processing by the server), you will need to employ a
16757// workaround on the server side: Send back an HTML document containing just a
16758// `<textarea>` element with a `data-type` attribute that specifies the MIME
16759// type, and put the actual payload in the textarea:
16760
16761//     <textarea data-type="application/json">
16762//       {"ok": true, "message": "Thanks so much"}
16763//     </textarea>
16764
16765// The iframe transport plugin will detect this and pass the value of the
16766// `data-type` attribute on to jQuery as if it was the "Content-Type" response
16767// header, thereby enabling the same kind of conversions that jQuery applies
16768// to regular responses. For the example above you should get a Javascript
16769// object as the `data` parameter of the `complete` callback, with the
16770// properties `ok: true` and `message: "Thanks so much"`.
16771
16772// ### Handling Server Errors
16773
16774// Another problem with using an `iframe` for file uploads is that it is
16775// impossible for the javascript code to determine the HTTP status code of the
16776// servers response. Effectively, all of the calls you make will look like they
16777// are getting successful responses, and thus invoke the `done()` or
16778// `complete()` callbacks. You can only determine communicate problems using
16779// the content of the response payload. For example, consider using a JSON
16780// response such as the following to indicate a problem with an uploaded file:
16781
16782//     <textarea data-type="application/json">
16783//       {"ok": false, "message": "Please only upload reasonably sized files."}
16784//     </textarea>
16785
16786// ### Compatibility
16787
16788// This plugin has primarily been tested on Safari 5 (or later), Firefox 4 (or
16789// later), and Internet Explorer (all the way back to version 6). While I
16790// haven't found any issues with it so far, I'm fairly sure it still doesn't
16791// work around all the quirks in all different browsers. But the code is still
16792// pretty simple overall, so you should be able to fix it and contribute a
16793// patch :)
16794
16795// ## Annotated Source
16796
16797(function($, undefined) {
16798  "use strict";
16799
16800  // Register a prefilter that checks whether the `iframe` option is set, and
16801  // switches to the "iframe" data type if it is `true`.
16802  $.ajaxPrefilter(function(options, origOptions, jqXHR) {
16803    if (options.iframe) {
16804      return "iframe";
16805    }
16806  });
16807
16808  // Register a transport for the "iframe" data type. It will only activate
16809  // when the "files" option has been set to a non-empty list of enabled file
16810  // inputs.
16811  $.ajaxTransport("iframe", function(options, origOptions, jqXHR) {
16812    var form = null,
16813        iframe = null,
16814        name = "iframe-" + $.now(),
16815        files = $(options.files).filter(":file:enabled"),
16816        markers = null,
16817        accepts;
16818
16819    // This function gets called after a successful submission or an abortion
16820    // and should revert all changes made to the page to enable the
16821    // submission via this transport.
16822    function cleanUp() {
16823      markers.each(function(i){
16824        $(this).replaceWith(files[i]);
16825        markers.splice(i, 1);
16826      });
16827      form.remove();
16828      iframe.bind("load", function() { iframe.remove(); });
16829      iframe.attr("src", "about:blank");
16830    }
16831
16832    // Remove "iframe" from the data types list so that further processing is
16833    // based on the content type returned by the server, without attempting an
16834    // (unsupported) conversion from "iframe" to the actual type.
16835    options.dataTypes.shift();
16836
16837    if (files.length) {
16838      form = $("<form enctype='multipart/form-data' method='post'></form>").
16839        hide().attr({action: options.url, target: name});
16840
16841      // If there is any additional data specified via the `data` option,
16842      // we add it as hidden fields to the form. This (currently) requires
16843      // the `processData` option to be set to false so that the data doesn't
16844      // get serialized to a string.
16845      if (typeof(options.data) === "string" && options.data.length > 0) {
16846        $.error("data must not be serialized");
16847      }
16848      $.each(options.data || {}, function(name, value) {
16849        if ($.isPlainObject(value)) {
16850          name = value.name;
16851          value = value.value;
16852        }
16853        $("<input type='hidden' />").attr({name:  name, value: value}).
16854          appendTo(form);
16855      });
16856
16857      // Add a hidden `X-Requested-With` field with the value `IFrame` to the
16858      // field, to help server-side code to determine that the upload happened
16859      // through this transport.
16860      $("<input type='hidden' value='IFrame' name='X-Requested-With' />").
16861        appendTo(form);
16862
16863      // Borrowed straight from the JQuery source
16864      // Provides a way of specifying the accepted data type similar to HTTP_ACCEPTS
16865      accepts = options.dataTypes[ 0 ] && options.accepts[ options.dataTypes[0] ] ?
16866        options.accepts[ options.dataTypes[0] ] + ( options.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
16867        options.accepts[ "*" ]
16868
16869      $("<input type='hidden' name='X-Http-Accept'>")
16870        .attr("value", accepts).appendTo(form);
16871
16872      // Move the file fields into the hidden form, but first remember their
16873      // original locations in the document by replacing them with disabled
16874      // clones. This should also avoid introducing unwanted changes to the
16875      // page layout during submission.
16876      markers = files.after(function(idx) {
16877        return $(this).clone().prop("disabled", true);
16878      }).next();
16879      files.appendTo(form);
16880
16881      return {
16882
16883        // The `send` function is called by jQuery when the request should be
16884        // sent.
16885        send: function(headers, completeCallback) {
16886          iframe = $("<iframe src='about:blank' name='" + name +
16887            "' id='" + name + "' style='display:none'></iframe>");
16888
16889          // The first load event gets fired after the iframe has been injected
16890          // into the DOM, and is used to prepare the actual submission.
16891          iframe.bind("load", function() {
16892
16893            // The second load event gets fired when the response to the form
16894            // submission is received. The implementation detects whether the
16895            // actual payload is embedded in a `<textarea>` element, and
16896            // prepares the required conversions to be made in that case.
16897            iframe.unbind("load").bind("load", function() {
16898              var doc = this.contentWindow ? this.contentWindow.document :
16899                (this.contentDocument ? this.contentDocument : this.document),
16900                root = doc.documentElement ? doc.documentElement : doc.body,
16901                textarea = root.getElementsByTagName("textarea")[0],
16902                type = textarea && textarea.getAttribute("data-type") || null,
16903                status = textarea && textarea.getAttribute("data-status") || 200,
16904                statusText = textarea && textarea.getAttribute("data-statusText") || "OK",
16905                content = {
16906                  html: root.innerHTML,
16907                  text: type ?
16908                    textarea.value :
16909                    root ? (root.textContent || root.innerText) : null
16910                };
16911              cleanUp();
16912              if (!jqXHR.responseText) {
16913                jqXHR.responseText = content.text;
16914              }
16915              completeCallback(status, statusText, content, type ?
16916                ("Content-Type: " + type) :
16917                null);
16918            });
16919
16920            // Now that the load handler has been set up, submit the form.
16921            form[0].submit();
16922          });
16923
16924          // After everything has been set up correctly, the form and iframe
16925          // get injected into the DOM so that the submission can be
16926          // initiated.
16927          $("body").append(form, iframe);
16928        },
16929
16930        // The `abort` function is called by jQuery when the request should be
16931        // aborted.
16932        abort: function() {
16933          if (iframe !== null) {
16934            iframe.unbind("load").attr("src", "javascript:false;");
16935            cleanUp();
16936          }
16937        }
16938
16939      };
16940    }
16941  });
16942
16943})(jQuery);
16944/*!
16945 * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
16946 * Dual-licensed under the BSD or MIT licenses
16947 */
16948;(function($, window, document, undefined)
16949{
16950    var hasTouch = 'ontouchstart' in document;
16951
16952    var nestableIdCounter = 0;
16953
16954    /**
16955     * Detect CSS pointer-events property
16956     * events are normally disabled on the dragging element to avoid conflicts
16957     * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
16958     */
16959    var hasPointerEvents = (function()
16960    {
16961        var el    = document.createElement('div'),
16962            docEl = document.documentElement;
16963        if (!('pointerEvents' in el.style)) {
16964            return false;
16965        }
16966        el.style.pointerEvents = 'auto';
16967        el.style.pointerEvents = 'x';
16968        docEl.appendChild(el);
16969        var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
16970        docEl.removeChild(el);
16971        return !!supports;
16972    })();
16973
16974    var defaults = {
16975            listNodeName    : 'ol',
16976            itemNodeName    : 'li',
16977            rootClass       : 'dd',
16978            listClass       : 'dd-list',
16979            itemClass       : 'dd-item',
16980            dragClass       : 'dd-dragel',
16981            handleClass     : 'dd-handle',
16982            collapsedClass  : 'dd-collapsed',
16983            placeClass      : 'dd-placeholder',
16984            noDragClass     : 'dd-nodrag',
16985            emptyClass      : 'dd-empty',
16986            expandBtnHTML   : '<button data-action="expand" type="button">Expand</button>',
16987            collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
16988            group           : 0,
16989            maxDepth        : 3,
16990            threshold       : 20
16991        };
16992
16993    function Plugin(element, options)
16994    {
16995        this.w  = $(document);
16996        this.el = $(element);
16997        this.options = $.extend({}, defaults, options);
16998        this.init();
16999    }
17000
17001    Plugin.prototype = {
17002
17003        init: function()
17004        {
17005            var list = this;
17006
17007            list.reset();
17008
17009            list.el.data('nestable-group', this.options.group);
17010
17011            list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
17012
17013            $.each(this.el.find(list.options.itemNodeName), function(k, el) {
17014                list.setParent($(el));
17015            });
17016
17017            list.el.on('click', 'button', function(e) {
17018                if (list.dragEl) {
17019                    return;
17020                }
17021                var target = $(e.currentTarget),
17022                    action = target.data('action'),
17023                    item   = target.parent(list.options.itemNodeName);
17024                if (action === 'collapse') {
17025                    list.collapseItem(item);
17026                }
17027                if (action === 'expand') {
17028                    list.expandItem(item);
17029                }
17030            });
17031
17032            var onStartEvent = function(e)
17033            {
17034                var handle = $(e.target);
17035                if (!handle.hasClass(list.options.handleClass)) {
17036                    if (handle.closest('.' + list.options.noDragClass).length) {
17037                        return;
17038                    }
17039                    handle = handle.closest('.' + list.options.handleClass);
17040                }
17041
17042                if (!handle.length || list.dragEl) {
17043                    return;
17044                }
17045
17046                list.isTouch = /^touch/.test(e.type);
17047                if (list.isTouch && e.touches.length !== 1) {
17048                    return;
17049                }
17050
17051                e.preventDefault();
17052                list.dragStart(e.touches ? e.touches[0] : e);
17053            };
17054
17055            var onMoveEvent = function(e)
17056            {
17057                if (list.dragEl) {
17058                    e.preventDefault();
17059                    list.dragMove(e.touches ? e.touches[0] : e);
17060                }
17061            };
17062
17063            var onEndEvent = function(e)
17064            {
17065                if (list.dragEl) {
17066                    e.preventDefault();
17067                    list.dragStop(e.touches ? e.touches[0] : e);
17068                }
17069            };
17070
17071            if (hasTouch) {
17072                list.el[0].addEventListener('touchstart', onStartEvent, false);
17073                window.addEventListener('touchmove', onMoveEvent, false);
17074                window.addEventListener('touchend', onEndEvent, false);
17075                window.addEventListener('touchcancel', onEndEvent, false);
17076            }
17077
17078            list.el.on('mousedown', onStartEvent);
17079            list.w.on('mousemove', onMoveEvent);
17080            list.w.on('mouseup', onEndEvent);
17081
17082        },
17083
17084        serialize: function()
17085        {
17086            var data,
17087                depth = 0,
17088                list  = this;
17089                step  = function(level, depth)
17090                {
17091                    var array = [ ],
17092                        items = level.children(list.options.itemNodeName);
17093                    items.each(function()
17094                    {
17095                        var li   = $(this),
17096                            item = $.extend({}, li.data()),
17097                            sub  = li.children(list.options.listNodeName);
17098                        if (sub.length) {
17099                            item.children = step(sub, depth + 1);
17100                        }
17101                        array.push(item);
17102                    });
17103                    return array;
17104                };
17105            data = step(list.el.find(list.options.listNodeName).first(), depth);
17106            return data;
17107        },
17108
17109        serialise: function()
17110        {
17111            return this.serialize();
17112        },
17113
17114        reset: function()
17115        {
17116            this.mouse = {
17117                offsetX   : 0,
17118                offsetY   : 0,
17119                startX    : 0,
17120                startY    : 0,
17121                lastX     : 0,
17122                lastY     : 0,
17123                nowX      : 0,
17124                nowY      : 0,
17125                distX     : 0,
17126                distY     : 0,
17127                dirAx     : 0,
17128                dirX      : 0,
17129                dirY      : 0,
17130                lastDirX  : 0,
17131                lastDirY  : 0,
17132                distAxX   : 0,
17133                distAxY   : 0
17134            };
17135            this.isTouch    = false;
17136            this.moving     = false;
17137            this.dragEl     = null;
17138            this.dragRootEl = null;
17139            this.dragDepth  = 0;
17140            this.hasNewRoot = false;
17141            this.pointEl    = null;
17142        },
17143
17144        expandItem: function(li)
17145        {
17146            li.removeClass(this.options.collapsedClass);
17147            li.children('[data-action="expand"]').hide();
17148            li.children('[data-action="collapse"]').show();
17149            li.children(this.options.listNodeName).show();
17150        },
17151
17152        collapseItem: function(li)
17153        {
17154            var lists = li.children(this.options.listNodeName);
17155            if (lists.length) {
17156                li.addClass(this.options.collapsedClass);
17157                li.children('[data-action="collapse"]').hide();
17158                li.children('[data-action="expand"]').show();
17159                li.children(this.options.listNodeName).hide();
17160            }
17161        },
17162
17163        expandAll: function()
17164        {
17165            var list = this;
17166            list.el.find(list.options.itemNodeName).each(function() {
17167                list.expandItem($(this));
17168            });
17169        },
17170
17171        collapseAll: function()
17172        {
17173            var list = this;
17174            list.el.find(list.options.itemNodeName).each(function() {
17175                list.collapseItem($(this));
17176            });
17177        },
17178
17179        setParent: function(li)
17180        {
17181            if (li.children(this.options.listNodeName).length) {
17182                li.prepend($(this.options.expandBtnHTML));
17183                li.prepend($(this.options.collapseBtnHTML));
17184            }
17185            li.children('[data-action="expand"]').hide();
17186        },
17187
17188        unsetParent: function(li)
17189        {
17190            li.removeClass(this.options.collapsedClass);
17191            li.children('[data-action]').remove();
17192            li.children(this.options.listNodeName).remove();
17193        },
17194
17195        dragStart: function(e)
17196        {
17197            var mouse    = this.mouse,
17198                target   = $(e.target),
17199                dragItem = target.closest(this.options.itemNodeName);
17200
17201            this.placeEl.css('height', dragItem.height());
17202
17203            mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
17204            mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
17205            mouse.startX = mouse.lastX = e.pageX;
17206            mouse.startY = mouse.lastY = e.pageY;
17207
17208            this.dragRootEl = this.el;
17209
17210            this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
17211            this.dragEl.css('width', dragItem.width());
17212
17213            dragItem.after(this.placeEl);
17214            dragItem[0].parentNode.removeChild(dragItem[0]);
17215            dragItem.appendTo(this.dragEl);
17216
17217            $(document.body).append(this.dragEl);
17218            this.dragEl.css({
17219                'left' : e.pageX - mouse.offsetX,
17220                'top'  : e.pageY - mouse.offsetY
17221            });
17222            // total depth of dragging item
17223            var i, depth,
17224                items = this.dragEl.find(this.options.itemNodeName);
17225            for (i = 0; i < items.length; i++) {
17226                depth = $(items[i]).parents(this.options.listNodeName).length;
17227                if (depth > this.dragDepth) {
17228                    this.dragDepth = depth;
17229                }
17230            }
17231        },
17232
17233        dragStop: function(e)
17234        {
17235            var el = this.dragEl.children(this.options.itemNodeName).first();
17236            el[0].parentNode.removeChild(el[0]);
17237            this.placeEl.replaceWith(el);
17238
17239            this.dragEl.remove();
17240            this.el.trigger('change');
17241            if (this.hasNewRoot) {
17242                this.dragRootEl.trigger('change');
17243            }
17244            this.reset();
17245        },
17246
17247        dragMove: function(e)
17248        {
17249            var list, parent, prev, next, depth,
17250                opt   = this.options,
17251                mouse = this.mouse;
17252
17253            this.dragEl.css({
17254                'left' : e.pageX - mouse.offsetX,
17255                'top'  : e.pageY - mouse.offsetY
17256            });
17257
17258            // mouse position last events
17259            mouse.lastX = mouse.nowX;
17260            mouse.lastY = mouse.nowY;
17261            // mouse position this events
17262            mouse.nowX  = e.pageX;
17263            mouse.nowY  = e.pageY;
17264            // distance mouse moved between events
17265            mouse.distX = mouse.nowX - mouse.lastX;
17266            mouse.distY = mouse.nowY - mouse.lastY;
17267            // direction mouse was moving
17268            mouse.lastDirX = mouse.dirX;
17269            mouse.lastDirY = mouse.dirY;
17270            // direction mouse is now moving (on both axis)
17271            mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
17272            mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
17273            // axis mouse is now moving on
17274            var newAx   = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
17275
17276            // do nothing on first move
17277            if (!mouse.moving) {
17278                mouse.dirAx  = newAx;
17279                mouse.moving = true;
17280                return;
17281            }
17282
17283            // calc distance moved on this axis (and direction)
17284            if (mouse.dirAx !== newAx) {
17285                mouse.distAxX = 0;
17286                mouse.distAxY = 0;
17287            } else {
17288                mouse.distAxX += Math.abs(mouse.distX);
17289                if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
17290                    mouse.distAxX = 0;
17291                }
17292                mouse.distAxY += Math.abs(mouse.distY);
17293                if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
17294                    mouse.distAxY = 0;
17295                }
17296            }
17297            mouse.dirAx = newAx;
17298
17299            /**
17300             * move horizontal
17301             */
17302            if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
17303                // reset move distance on x-axis for new phase
17304                mouse.distAxX = 0;
17305                prev = this.placeEl.prev(opt.itemNodeName);
17306                // increase horizontal level if previous sibling exists and is not collapsed
17307                if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
17308                    // cannot increase level when item above is collapsed
17309                    list = prev.find(opt.listNodeName).last();
17310                    // check if depth limit has reached
17311                    depth = this.placeEl.parents(opt.listNodeName).length;
17312                    if (depth + this.dragDepth <= opt.maxDepth) {
17313                        // create new sub-level if one doesn't exist
17314                        if (!list.length) {
17315                            list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
17316                            list.append(this.placeEl);
17317                            prev.append(list);
17318                            this.setParent(prev);
17319                        } else {
17320                            // else append to next level up
17321                            list = prev.children(opt.listNodeName).last();
17322                            list.append(this.placeEl);
17323                        }
17324                    }
17325                }
17326                // decrease horizontal level
17327                if (mouse.distX < 0) {
17328                    // we can't decrease a level if an item preceeds the current one
17329                    next = this.placeEl.next(opt.itemNodeName);
17330                    if (!next.length) {
17331                        parent = this.placeEl.parent();
17332                        this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
17333                        if (!parent.children().length) {
17334                            this.unsetParent(parent.parent());
17335                        }
17336                    }
17337                }
17338            }
17339
17340            var isEmpty = false;
17341
17342            // find list item under cursor
17343            if (!hasPointerEvents) {
17344                this.dragEl[0].style.visibility = 'hidden';
17345            }
17346            this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
17347            if (!hasPointerEvents) {
17348                this.dragEl[0].style.visibility = 'visible';
17349            }
17350            if (this.pointEl.hasClass(opt.handleClass)) {
17351                this.pointEl = this.pointEl.parent(opt.itemNodeName);
17352            }
17353            if (this.pointEl.hasClass(opt.emptyClass)) {
17354                isEmpty = true;
17355            }
17356            else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
17357                return;
17358            }
17359
17360            // find parent list of item under cursor
17361            var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
17362                isNewRoot   = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
17363
17364            /**
17365             * move vertical
17366             */
17367            if (!mouse.dirAx || isNewRoot || isEmpty) {
17368                // check if groups match if dragging over new root
17369                if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
17370                    return;
17371                }
17372                // check depth limit
17373                depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
17374                if (depth > opt.maxDepth) {
17375                    return;
17376                }
17377                var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
17378                    parent = this.placeEl.parent();
17379                // if empty create new list to replace empty placeholder
17380                if (isEmpty) {
17381                    list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
17382                    list.append(this.placeEl);
17383                    this.pointEl.replaceWith(list);
17384                }
17385                else if (before) {
17386                    this.pointEl.before(this.placeEl);
17387                }
17388                else {
17389                    this.pointEl.after(this.placeEl);
17390                }
17391                if (!parent.children().length) {
17392                    this.unsetParent(parent.parent());
17393                }
17394                if (!this.dragRootEl.find(opt.itemNodeName).length && !this.dragRootEl.find('div.'+opt.placeClass).length && !this.dragRootEl.find('div.'+opt.emptyClass).length) {
17395                  this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>');
17396                }
17397                // parent root list has changed
17398                if (isNewRoot) {
17399                    this.dragRootEl = pointElRoot;
17400                    this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
17401                }
17402            }
17403        }
17404
17405    };
17406
17407    $.fn.nestable = function(params)
17408    {
17409        var lists  = this,
17410            retval = this;
17411
17412        lists.each(function()
17413        {
17414            var plugin = $(this).data("nestable");
17415
17416            if (!plugin) {
17417                $(this).data("nestable", new Plugin(this, params));
17418                $(this).data("nestable-id", ++nestableIdCounter);
17419            } else {
17420                if (typeof params === 'string' && typeof plugin[params] === 'function') {
17421                    retval = plugin[params]();
17422                }
17423            }
17424        });
17425
17426        return retval || lists;
17427    };
17428
17429})(window.jQuery || window.Zepto, window, document);
17430
17431
17432
17433(function($) {
17434
17435  var remotipart;
17436
17437  $.remotipart = remotipart = {
17438
17439    setup: function(form) {
17440      // Preserve form.data('ujs:submit-button') before it gets nulled by $.ajax.handleRemote
17441      var button = form.data('ujs:submit-button'),
17442          csrfParam = $('meta[name="csrf-param"]').attr('content'),
17443          csrfToken = $('meta[name="csrf-token"]').attr('content'),
17444          csrfInput = form.find('input[name="' + csrfParam + '"]').length;
17445
17446      form
17447        // Allow setup part of $.rails.handleRemote to setup remote settings before canceling default remote handler
17448        // This is required in order to change the remote settings using the form details
17449        .one('ajax:beforeSend.remotipart', function(e, xhr, settings){
17450          // Delete the beforeSend bindings, since we're about to re-submit via ajaxSubmit with the beforeSubmit
17451          // hook that was just setup and triggered via the default `$.rails.handleRemote`
17452          // delete settings.beforeSend;
17453          delete settings.beforeSend;
17454
17455          settings.iframe      = true;
17456          settings.files       = $($.rails.fileInputSelector, form);
17457          settings.data        = form.serializeArray();
17458
17459          // Insert the name/value of the clicked submit button, if any
17460          if (button)
17461            settings.data.push(button);
17462
17463          // jQuery 1.9 serializeArray() contains input:file entries
17464          // so exclude them from settings.data, otherwise files will not be sent
17465          settings.files.each(function(i, file){
17466            for (var j = settings.data.length - 1; j >= 0; j--)
17467              if (settings.data[j].name == file.name)
17468                settings.data.splice(j, 1);
17469          })
17470
17471          settings.processData = false;
17472
17473          // Modify some settings to integrate JS request with rails helpers and middleware
17474          if (settings.dataType === undefined) { settings.dataType = 'script *'; }
17475          settings.data.push({name: 'remotipart_submitted', value: true});
17476          if (csrfToken && csrfParam && !csrfInput) {
17477            settings.data.push({name: csrfParam, value: csrfToken});
17478          }
17479
17480          // Allow remotipartSubmit to be cancelled if needed
17481          if ($.rails.fire(form, 'ajax:remotipartSubmit', [xhr, settings])) {
17482            // Second verse, same as the first
17483            $.rails.ajax(settings).complete(function(data){
17484              $.rails.fire(form, 'ajax:remotipartComplete', [data]);
17485            });
17486            setTimeout(function(){ $.rails.disableFormElements(form); }, 20);
17487          }
17488
17489          //Run cleanup
17490          remotipart.teardown(form);
17491
17492          // Cancel the jQuery UJS request
17493          return false;
17494        })
17495
17496        // Keep track that we just set this particular form with Remotipart bindings
17497        // Note: The `true` value will get over-written with the `settings.dataType` from the `ajax:beforeSend` handler
17498        .data('remotipartSubmitted', true);
17499    },
17500
17501    teardown: function(form) {
17502      form
17503        .unbind('ajax:beforeSend.remotipart')
17504        .removeData('remotipartSubmitted')
17505    }
17506  };
17507
17508  $(document).on('ajax:aborted:file', 'form', function(){
17509    var form = $(this);
17510
17511    remotipart.setup(form);
17512
17513    // Manually call jquery-ujs remote call so that it can setup form and settings as usual,
17514    // and trigger the `ajax:beforeSend` callback to which remotipart binds functionality.
17515    $.rails.handleRemote(form);
17516    return false;
17517  });
17518
17519})(jQuery);
17520
17521function start_matrix() {
17522
17523  $('ol.matrix').each(function(){
17524
17525    refresh_matrix($(this));
17526    $('div.checkbox.active input', $(this)).change(function(){
17527      refresh_matrix($(this).parents('ol.matrix'));
17528      $('div.checkbox.required input', $(this).parents('li.row')).prop('checked', true);
17529    });
17530
17531  });
17532
17533}
17534
17535function refresh_matrix($matrix) {
17536
17537  $('div.checkbox.active', $matrix).each(function(){
17538    if ($('input', $(this)).is(':checked')) {
17539      $('div.checkbox.required', $(this).parents('li.row')).show();
17540      $('div.label', $(this).parents('li.row')).show();
17541    } else {
17542      $('div.checkbox.required', $(this).parents('li.row')).hide();
17543      $('div.label', $(this).parents('li.row')).hide();
17544    }
17545  });
17546
17547}
17548
17549function start_matrix_sort() {
17550
17551  $('ol.matrix').each(function(){
17552    refresh_matrix_sort($(this));
17553  });
17554
17555  $('div.matrixsort').nestable({
17556    maxDepth: 1,
17557    rootClass: 'matrixsort',
17558    listClass: 'matrix',
17559    itemClass: 'row',
17560    dragClass: 'dragging',
17561    handleClass: 'weight',
17562    placeClass: 'place'
17563  });
17564  $('div.matrixsort').on('change', function() {
17565    refresh_matrix_sort($('ol.matrix'));
17566  });
17567
17568}
17569
17570function refresh_matrix_sort($sortlist) {
17571
17572  $('select[id$="weight"]', $sortlist).each(function(index){
17573    $(this).val($(this).parents('li').index()+1);
17574  });
17575
17576};
17577
17578function start_modal(content) {
17579
17580  $('body').addClass('modal');
17581
17582  $('#cover').fadeIn('fast');
17583  $('#cover').click(function(){
17584    //close_modal();
17585  });
17586
17587  $('#modal').html(content+'<div class="white">.</div>');
17588  $('#window').show();
17589  $('#window').click(function(e) {
17590    if (e.target == this) {
17591      //close_modal();
17592    }
17593  });
17594
17595  $('#window .close').click(function(event){
17596    close_modal();
17597    event.preventDefault();
17598  });
17599
17600  set_cursor($('#modal form:first'));
17601
17602}
17603
17604function close_modal() {
17605
17606  $('#modal').html('');
17607  $('#window').hide();
17608  $('#cover').fadeOut('fast', function(){
17609    $('body').removeClass('modal');
17610  });
17611
17612};
17613function start_match_panels($panels, $target) {
17614
17615  resize_match_panels($panels, $target);
17616  $(window).on('resize', function(){
17617    resize_match_panels($panels, $target);
17618  });
17619
17620}
17621
17622function resize_match_panels($panels, $target) {
17623
17624  $('div.panel '+$target, $panels).css('min-height', '0px');
17625  $max_height = 0;
17626  $('div.panel '+$target, $panels).each(function(){
17627    if ($(this).height() > $max_height) {
17628      $max_height = $(this).height();
17629    }
17630  });
17631  $('div.panel '+$target, $panels).css('min-height', $max_height+'px');
17632
17633};
17634
17635function start_picker() {
17636
17637  $('input.hex').each(function(){
17638    set_picker_style($(this));
17639  });
17640
17641  $('a.picker').each(function(){
17642
17643    var $field = $(this).data('field');
17644
17645    var $swatch = $('<div data-field="'+$field+'" class="popup swatch"></div>');
17646    $swatch.append('<div class="close">&times;</div>');
17647    $swatch.append('<canvas id="'+$field+'_swatch" width="320" height="240" />');
17648
17649    $(this).parents('div.field').append($swatch);
17650
17651    var $context = document.getElementById($field+'_swatch').getContext('2d');
17652
17653    var img = new Image();
17654    img.src = '/assets/picker-bf36f247dd81d0bdda18cf27e78a8db916cb4c0c4323d6637bd8a23b6bf4e06d.png';
17655    $(img).load(function(){
17656      $context.drawImage(img,0,0)
17657    });
17658
17659    $('div.colour', $swatch).each(function(){
17660      $(this).css('background-color', $(this).data('colour'));
17661    });
17662
17663    $('div.colour', $swatch).click(function(){
17664      $input = $('input.hex', $(this).parents('div.field'))
17665      $input.val($(this).data('colour'));
17666      set_picker_style($input);
17667    });
17668
17669    $('canvas', $swatch).click(function(event){
17670
17671  	  // getting user coordinates
17672  	  var x = parseInt((event.pageX - $(this).offset().left),10);
17673  	  var y = parseInt((event.pageY - $(this).offset().top),10);
17674  	  // getting image data and RGB values
17675  	  var img_data = $context.getImageData(x, y, 1, 1).data;
17676      console.log(img_data);
17677  	  var R = img_data[0];
17678  	  var G = img_data[1];
17679  	  var B = img_data[2];
17680      var rgb = R + ',' + G + ',' + B;
17681  	  // convert RGB to HEX
17682  	  var hex = rgbToHex(R,G,B);
17683  	  // making the color the value of the input
17684
17685      $input = $('input.hex', $(this).parents('div.field'))
17686      $input.val('#' + hex);
17687      set_picker_style($input);
17688
17689  	});
17690
17691    $('div.close', $swatch).click(function(){
17692      $(this).parents('div.swatch').hide();
17693    });
17694
17695    $swatch.hide();
17696  });
17697  $('input.hex').keyup(function(){
17698    set_picker_style($(this));
17699  });
17700  $('input.hex').change(function(){
17701    set_picker_style($(this));
17702  });
17703  $('a.picker').click(function($event){
17704    $('div.swatch', $(this).parents('div.field')).toggle();
17705    $event.preventDefault();
17706  });
17707
17708}
17709
17710function rgbToHex(R,G,B) {return toHex(R)+toHex(G)+toHex(B)}
17711function toHex(n) {
17712  n = parseInt(n,10);
17713  if (isNaN(n)) return "00";
17714  n = Math.max(0,Math.min(n,255));
17715  return "0123456789ABCDEF".charAt((n-n%16)/16)  + "0123456789ABCDEF".charAt(n%16);
17716}
17717
17718function is_dark($colour) {
17719  if (parseInt($colour.replace('#', ''), 16) < (0xffffff / 1.5)) {
17720    return true;
17721  } else {
17722    return false;
17723  }
17724}
17725
17726function set_picker_style($picker) {
17727
17728  if (($picker.val().length == 4)||($picker.val().length == 7)) {
17729    $('div.square', $picker.parents('div.field')).css('background-color', $picker.val());
17730  }
17731
17732};
17733
17734function start_preview() {
17735
17736  $('div.preview a').click(function(e){e.preventDefault()});
17737  $('input').change(refresh_preview);
17738  $('input').keyup(refresh_preview);
17739  $('select').change(refresh_preview);
17740  $('textarea').change(refresh_preview);
17741  $('textarea').keyup(refresh_preview);
17742
17743  refresh_preview();
17744
17745}
17746
17747function refresh_preview() {
17748
17749  $('div.domain').each(function(){
17750    $(this).hide();
17751    if ($('input#tenant_subdomain').val() != '') {
17752      $('input#tenant_subdomain').val($('input#tenant_subdomain').val().replace('\'',''));
17753      $('pre', this).html('https://'+$('input#tenant_subdomain').val()+'.whitefuse.net');
17754      $(this).show();
17755    }
17756  });
17757
17758  $('div.row.logo').each(function(){
17759    $('div.frame', $(this)).css('background-color', $('input[id^=theme_colour_header_back]:checked').val());
17760  });
17761
17762  $('div.preview.fonts').each(function(){
17763    $('.heading1', this).css('font-family', $('input[id^=theme_font_heading1]:checked').val());
17764    $('.heading1', this).css('font-weight', $('input[id^=theme_heading1_font_weight]:checked').val());
17765    $('.heading2', this).css('font-family', $('input[id^=theme_font_heading2]:checked').val());
17766    $('.heading2', this).css('font-weight', $('input[id^=theme_heading2_font_weight]:checked').val());
17767    $('.heading3', this).css('font-family', $('input[id^=theme_font_heading3]:checked').val());
17768    $('.heading3', this).css('font-weight', $('input[id^=theme_heading3_font_weight]:checked').val());
17769    $('.heading4', this).css('font-family', $('input[id^=theme_font_heading4]:checked').val());
17770    $('.heading4', this).css('font-weight', $('input[id^=theme_heading4_font_weight]:checked').val());
17771    $('.quote', this).css('font-family', $('input[id^=theme_font_quote]:checked').val());
17772    $('.quote', this).css('font-weight', $('input[id^=theme_quote_font_weight]:checked').val());
17773  });
17774
17775  $('div.preview.link').each(function(){
17776    $('a', this).css('color', $('input[id^=theme_colour_link_text]:checked').val());
17777  });
17778
17779  $('div.preview.button').each(function(){
17780    $('a.button', this).css('color', $('input#theme_colour_button_text').val());
17781    $('a.button', this).css('background-color', $('input[id^=theme_colour_button_back]:checked').val());
17782    switch ($('input[id^=theme_button_radius]:checked').val()) {
17783      case 'small':
17784        $('a.button', this).css('padding', '8px 10px');
17785        $('a.button', this).css('border-radius', '2px');
17786        break;
17787      case 'large':
17788        $('a.button', this).css('padding', '8px 15px');
17789        $('a.button', this).css('border-radius', '100px');
17790        break;
17791    }
17792  });
17793
17794  $('div.preview.signpost').each(function(){
17795    $('span.title', this).html($('input#signpost_title').val());
17796    $('span.body', this).html($('textarea#signpost_body').val());
17797    $('a.signpost', this).css('color', $('input#theme_colour_signpost_text').val());
17798    $('a.signpost', this).css('background-color', $('input[id^=theme_colour_signpost_back]:checked').val());
17799    $('span.title', this).css('font-family', $('input[id^=theme_font_signpost_title]:checked').val());
17800    $('span.title', this).css('font-weight', $('input[id^=theme_signpost_title_font_weight]:checked').val());
17801    switch ($('input[id^=theme_signpost_radius]:checked').val()) {
17802      case 'small':
17803        $('a.signpost', this).css('border-radius', '2px');
17804        break;
17805      case 'large':
17806        $('a.signpost', this).css('border-radius', '10px');
17807        break;
17808    }
17809  });
17810
17811  $('div.preview.header').each(function(){
17812    $('div.header', this).css('color', $('input#theme_colour_header_text').val());
17813    $('div.header', this).css('background-color', $('input[id^=theme_colour_header_back]:checked').val());
17814    $('div.header div.menu', this).css('font-family', $('input[id^=theme_menu_font_family]:checked').val());
17815    $('div.header div.menu', this).css('font-weight', $('input[id^=theme_header_menu_font_weight]:checked').val());
17816    $('div.header div.menu a', this).css('color', $('input[id^=theme_colour_menu_text]:checked').val());
17817    $('div.header div.menu a', this).css('background-color', $('input[id^=theme_colour_menu_back]:checked').val());
17818    switch ($('input[id^=theme_menu_font_size]:checked').val()) {
17819      case 'small':
17820        $('div.header div.menu', this).css('line-height', '22px');
17821        $('div.header div.menu', this).css('font-size', '16px');
17822        $('div.header div.menu a', this).css('padding', '5px 8px');
17823        break;
17824      case 'large':
17825        $('div.header div.menu', this).css('line-height', '26px');
17826        $('div.header div.menu', this).css('font-size', '20px');
17827        $('div.header div.menu a', this).css('padding', '5px 10px');
17828        break;
17829    }
17830    switch ($('input[id^=theme_menu_radius]:checked').val()) {
17831      case 'small':
17832        $('div.header div.menu a', this).css('border-radius', '2px');
17833        break;
17834      case 'large':
17835        $('div.header div.menu a', this).css('padding', '5px 10px');
17836        $('div.header div.menu a', this).css('border-radius', '100px');
17837        break;
17838    }
17839    if ($('input[id^=theme_number_menu_highlight]:checked').val()) {
17840      $('div.header div.menu a:last-child', this).css('color', '#FFFFFF');
17841      if (($('input[id^=theme_colour_menu_text]:checked').val() == $('input[id^=theme_colour_
178411]').val()) || ($('input[id^=theme_colour_menu_back]:checked').val() == $('input[id^=theme_colour_1]').val())) {
17842        $('div.header div.menu a:last-child', this).css('background-color', $('input[id^=theme_colour_2]').val());
17843      } else {
17844        $('div.header div.menu a:last-child', this).css('background-color', $('input[id^=theme_colour_1]').val());
17845      }
17846    }
17847  });
17848
17849  $('div.preview.banner').each(function(){
17850    $('div.banner', this).css('background-color', $('input[id^=theme_banner_cover_colour]:checked').val());
17851    switch ($('select[id^=theme_banner_cover_shade]').val()) {
17852      case '0':
17853        $('div.cover', this).css('opacity', '0');
17854        break;
17855      case '40':
17856        $('div.cover', this).css('opacity', '0.4');
17857        break;
17858      default:
17859        $('div.cover', this).css('opacity', '0.2');
17860        break;
17861    }
17862  });
17863
17864  $('div.preview.content').each(function(){
17865    if ($('input[id^=theme_colour_box_back]:checked').val() == '#F6F6F6') {
17866      $('div.box div.cover', this).hide();
17867      $('div.box', this).css('border-color', '#E0E0E0');
17868    } else {
17869      $('div.box div.cover', this).show();
17870      $('div.box', this).css('border-color', $('input[id^=theme_colour_box_back]:checked').val());
17871    }
17872    $('div.box', this).css('background-color', $('input[id^=theme_colour_box_back]:checked').val());
17873  });
17874
17875  $('div.preview.footer').each(function(){
17876    if ($('input[id^=theme_colour_ribbon_back]:checked').val() == '#F6F6F6') {
17877      $('div.ribbon', this).css('color', '#222222');
17878      $('div.ribbon div.cover', this).hide();
17879    } else {
17880      $('div.ribbon', this).css('color', '#FFFFFF');
17881      $('div.ribbon div.cover', this).show();
17882    }
17883    $('div.ribbon', this).css('background-color', $('input[id^=theme_colour_ribbon_back]:checked').val());
17884  });
17885
17886};
17887
17888function start_questions($type, $subtype) {
17889
17890  $('div.questions').each(function(){
17891    start_questions_delete($(this));
17892    $('select[id$="_input"]', $(this)).each(function(){
17893      refresh_input_options($(this));
17894      $(this).change(function(){
17895        refresh_input_options($(this));
17896      });
17897    });
17898    $('select[id$="_input"]', $(this)).change(function(){
17899      refresh_input_options($(this));
17900    });
17901    $('ol.options', $(this)).each(function(){
17902      create_add_option_link($(this), $type, $subtype);
17903    });
17904    create_add_question_link($(this), $type, $subtype);
17905  });
17906
17907}
17908
17909function start_questions_delete($questions) {
17910
17911  $('input.question.deactivate').click(function(){
17912    if ($(this).not(':checked')) {
17913      $(this).parents('section.question').hide();
17914    }
17915  })
17916
17917  $('input.question.delete').click(function(){
17918    if ($(this).is(':checked')) {
17919      $(this).parents('section.question').hide();
17920    }
17921  })
17922
17923  $('input.option.delete').click(function(){
17924    if ($(this).is(':checked')) {
17925      $(this).parents('li.option').hide();
17926    }
17927  })
17928
17929}
17930
17931function start_question_sort() {
17932
17933  $('div.questions').each(function(){
17934    refresh_question_sort($(this));
17935    $('a.raise', $(this)).click(function($event){
17936      raise_question($(this).parents('section.question'));
17937      $event.preventDefault();
17938    });
17939    $('a.lower', $(this)).click(function($event){
17940      lower_question($(this).parents('section.question'));
17941      $event.preventDefault();
17942    });
17943  });
17944
17945}
17946
17947function raise_question($question) {
17948  if ($question.prev().is('section')) {
17949    $question.prev().insertAfter($question);
17950    refresh_question_sort($question.parents('div.questions'));
17951  }
17952}
17953
17954function lower_question($question) {
17955  if ($question.next().is('section')) {
17956    $question.next().insertBefore($question);
17957    refresh_question_sort($question.parents('div.questions'));
17958  }
17959}
17960
17961function refresh_question_sort($questions) {
17962
17963  $('select[id$="weight"]', $questions).each(function(index){
17964    $(this).val($(this).parents('section').index()+1);
17965  });
17966
17967}
17968
17969function refresh_input_options($select) {
17970
17971  $('div.optionsort', $select.parents('div.input')).hide();
17972  if ($('option:selected', $select).attr('class') == 'options') {
17973    $('div.optionsort', $select.parents('div.input')).show();
17974  }
17975
17976}
17977
17978function create_add_question_link($questions, $type, $subtype) {
17979
17980  $actions = $('<div class="actions"></div>');
17981  $link = $('<a href="#" class="create mdi mdi-plus">Add a question</a>');
17982  $link.click(function($event){
17983    $count = $('section.question', $questions).length;
17984    $section_question = $('<section class="question box" data-row="'+$count+'"></section>');
17985    $div_actions = $('<div class="actions icons-1"></div>');
17986    $div_actions_delete = $('<a href="#" class="delete icon mdi mdi-delete"><span>
17986Delete</span></a>');
17987    $div_actions_delete.click(function($event){
17988      $(this).parents('section.question').remove();
17989      $event.preventDefault();
17990    });
17991    $div_actions.append($div_actions_delete);
17992    $section_question.append($div_actions);
17993    $div_weight = $('<div class="weight"></div>');
17994    $div_weight_raise = $('<a href="#" class="icon raise mdi mdi-menu-up"><span>Raise</span></a>');
17995    $div_weight_raise.click(function($event){
17996      raise_question($(this).parents('section.question'));
17997      refresh_question_sort($(this).parents('div.questions'));
17998      $event.preventDefault();
17999    });
18000    $div_weight.append($div_weight_raise);
18001    $div_weight_lower = $('<a href="#" class="icon lower mdi mdi-menu-down"><span>Lower</span></a>');
18002    $div_weight_lower.click(function($event){
18003      lower_question($(this).parents('section.question'));
18004      refresh_question_sort($(this).parents('div.questions'));
18005      $event.preventDefault();
18006    });
18007    $div_weight.append($div_weight_lower);
18008    $div_weight_row = $('<div class="row select weight"></div>');
18009    //$div_weight_row.append($('<div class="label"><label for="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_weight">Weight</label></div>'));
18010    $div_weight_row_field = $('<div class="field"></div>');
18011    $div_weight_row_field_select = $('<select name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][weight]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_weight"></select>');
18012    for (var $i = 0; $i <= 20; $i++) {
18013      if ($i == 0) {
18014        $div_weight_row_field_select.append($('<option selected="selected" value="0">0</option>'));
18015      } else {
18016        $div_weight_row_field_select.append($('<option value="'+$i+'">'+$i+'</option>'));
18017      }
18018    }
18019    $div_weight_row_field.append($div_weight_row_field_select);
18020    $div_weight_row.append($div_weight_row_field);
18021    $div_weight.append($div_weight_row);
18022    $section_question.append($div_weight);
18023    $div_label = $('<div class="label"></div>');
18024    $div_label.append($('<input type="hidden" name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][question_attributes][type]" value="SubmissionQuestion" />'));
18025    $div_label.append($('<div class="row text label snug"><div class="label"><label for="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_question_attributes_label">Question</label></div><div class="field"><input value="" size="50" type="text" name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][question_attributes][label]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_question_attributes_label"></div></div>'));
18026    $section_question.append($div_label);
18027    $section_question.append('&nbsp;');
18028    $div_required = $('<div class="required"></div>');
18029    $div_required.append($('<div class="row inline checkbox required"><div class="field"><div class="checkbox"><input name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][required]" type="hidden" value="0"><input type="checkbox" value="1" name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][required]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_required"> <label for="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_required">Require</label></div></div></div>'));
18030    $section_question.append($div_required);
18031    $div_input = $('<div class="input"></div>');
18032    $div_input_row = $('<div class="row select input"></div>');
18033    $div_input_row.append('<div class="label"><label for="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_question_attributes_input">Response</label></div>');
18034    $div_input_row_field = $('<div class="field"></div>');
18035    $div_input_row_field_select = $('<select name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][question_attributes][input]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_question_attributes_input"></select>');
18036    $div_input_row_field_select.append($('<option value="text" selected="selected">Text (single line)</option>'));
18037    $div_input_row_field_select.append($('<option value="textarea">Text (paragraph)</option>'));
18038    $div_input_row_field_select.append($('<option value="date">Date</option></select>'));
18039    $div_input_row_field_select.append($('<option class="options" value="select">Drop-down</option>'));
18040    $div_input_row_field_select.append($('<option class="options" value="radios">Radio buttons</option>'));
18041    $div_input_row_field_select.append($('<option class="options" value="checkboxes">Checkboxes</option>'));
18042    $div_input_row_field_select.change(function(){
18043      refresh_input_options($(this));
18044    });
18045    $div_input_row_field.append($div_input_row_field_select);
18046    $div_input_row.append($div_input_row_field);
18047    $div_input.append($div_input_row);
18048    $div_input_options = $('<div class="optionsort"></div>');
18049    $div_input_options_list = $('<ol class="options"></ol>');
18050    for (var $i = 0; $i < 2; $i++) {
18051      $div_input_options_list_li = $('<li class="option"></li>');
18052      $div_input_options_list_li.append($('<div class="weight"><span class="mdi mdi-drag-vertical"></span>&nbsp;
18052<div class="row select weight"><div class="field"><select name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][question_attributes][question_options_attributes]['+$i+'][weight]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_question_attributes_question_options_attributes_'+$i+'_weight"><option selected="selected" value="0">0</option><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option><option value="12">12</option><option value="13">13</option><option value="14">14</option><option value="15">15</option><option value="16">16</option><option value="17">17</option><option value="18">18</option><option value="19">19</option><option value="20">20</option></select></div></div></div>'));
18053      $div_input_options_list_li.append($('<div><div class="row text option"><div class="field"><input value="" size="50" type="text" name="'+$type+'['+$subtype+'_questions_attributes]['+$count+'][question_attributes][question_options_attributes]['+$i+'][option]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$count+'_question_attributes_question_options_attributes_'+$i+'_option" placeholder="Option"></div></div></div>'));
18054      $div_input_options_list_li.append($('<div class="actions icons-1"></div>'));
18055      $div_input_options_list.append($div_input_options_list_li);
18056    }
18057    $div_input_options.append($div_input_options_list);
18058    $div_input_options.hide();
18059    create_add_option_link($div_input_options_list, $type, $subtype);
18060    $div_input.append($div_input_options);
18061    $section_question.append($div_input);
18062    $questions.append($section_question);
18063    refresh_question_sort($questions);
18064    start_option_sort();
18065    $event.preventDefault();
18066  });
18067  $actions.append($link);
18068  $questions.after($actions);
18069
18070}
18071
18072function create_add_option_link($list, $type, $subtype) {
18073
18074  $actions = $('<div class="actions"></div>');
18075  var $link = $('<a href="#" class="create mdi mdi-plus">Add an option</a>');
18076  $link.click(function(event){
18077    $row = $(this).parents('section.question').data('row');
18078    $count = $('li.option', $list).length;
18079    $li_option = $('<li class="option"></li>');
18080    $div_weight = $('<div class="weight"></div>');
18081    $div_weight.append($('<span class="mdi mdi-drag-vertical"></span>'));
18082    $div_weight_row = $('<div class="row select weight"></div>');
18083    $div_weight_row.append($('<div class="field"><select name="'+$type+'['+$subtype+'_questions_attributes]['+$row+'][question_attributes][question_options_attributes]['+$count+'][weight]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$row+'_question_attributes_question_options_attributes_'+$count+'_weight"><option selected="selected" value="0">0</option><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option><option value="12">12</option><option value="13">13</option><option value="14">14</option><option value="15">15</option><option value="16">16</option><option value="17">17</option><option value="18">18</option><option value="19">19</option><option value="20">20</option></select></div>'));
18084    $div_weight.append($div_weight_row);
18085    $li_option.append($div_weight);
18086    $li_option.append('&nbsp;');
18087    $div_option = $('<div class="option"></div>');
18088    $div_option_row = $('<div class="row text option"></div>');
18089    $div_option_row.append($('<div class="field"><input size="50" type="text" name="'+$type+'['+$subtype+'_questions_attributes]['+$row+'][question_attributes][question_options_attributes]['+$count+'][option]" id="'+$type+'_'+$subtype+'_questions_attributes_'+$row+'_question_attributes_question_options_attributes_'+$count+'_option" placeholder="Option"></div>'));
18090    $div_option.append($div_option_row);
18091    $li_option.append($div_option);
18092    $li_option.append('&nbsp;');
18093    $div_actions = $('<div class="actions icons-1"></div>');
18094    $div_actions_delete = $('<a href="#" class="delete icon mdi mdi-delete"><span>
18094Delete</span></a>');
18095    $div_actions_delete.click(function($event){
18096      $(this).parents('li.option').remove();
18097      refresh_option_sort($('ol.options', $(this).parents('div.optionsort')));
18098      $event.preventDefault();
18099    });
18100    $div_actions.append($div_actions_delete);
18101    $li_option.append($div_actions);
18102    $list.append($li_option);
18103    refresh_option_sort($('ol.options', $(this).parents('div.optionsort')));
18104    event.preventDefault();
18105  });
18106  $actions.append($link);
18107  $list.after($actions);
18108
18109};
18110/*
18111	Redactor II
18112	Version 2.12
18113	Updated: December 4, 2017
18114
18115	http://imperavi.com/redactor/
18116
18117	Copyright (c) 2009-2017, Imperavi LLC.
18118	License: http://imperavi.com/redactor/license/
18119
18120	Usage: $('#content').redactor();
18121*/
18122
18123(function($)
18124{
18125	'use strict';
18126
18127	if (!Function.prototype.bind)
18128	{
18129		Function.prototype.bind = function(scope)
18130		{
18131			var fn = this;
18132			return function()
18133			{
18134				return fn.apply(scope);
18135			};
18136		};
18137	}
18138
18139	var uuid = 0;
18140
18141	// Plugin
18142	$.fn.redactor = function(options)
18143	{
18144		var val = [];
18145		var args = Array.prototype.slice.call(arguments, 1);
18146
18147		if (typeof options === 'string')
18148		{
18149			this.each(function()
18150			{
18151				var instance = $.data(this, 'redactor');
18152				var func;
18153
18154				if (options.search(/\./) !== '-1')
18155				{
18156					func = options.split('.');
18157					if (typeof instance[func[0]] !== 'undefined')
18158					{
18159						func = instance[func[0]][func[1]];
18160					}
18161				}
18162				else
18163				{
18164					func = instance[options];
18165				}
18166
18167				if (typeof instance !== 'undefined' && $.isFunction(func))
18168				{
18169					var methodVal = func.apply(instance, args);
18170					if (methodVal !== undefined && methodVal !== instance)
18171					{
18172						val.push(methodVal);
18173					}
18174				}
18175				else
18176				{
18177					$.error('No such method "' + options + '" for Redactor');
18178				}
18179			});
18180		}
18181		else
18182		{
18183			this.each(function()
18184			{
18185				$.data(this, 'redactor', {});
18186				$.data(this, 'redactor', Redactor(this, options));
18187			});
18188		}
18189
18190		if (val.length === 0)
18191		{
18192			return this;
18193		}
18194		else if (val.length === 1)
18195		{
18196			return val[0];
18197		}
18198		else
18199		{
18200			return val;
18201		}
18202
18203	};
18204
18205	// Initialization
18206	function Redactor(el, options)
18207	{
18208		return new Redactor.prototype.init(el, options);
18209	}
18210
18211	// Options
18212	$.Redactor = Redactor;
18213	$.Redactor.VERSION = '2.12';
18214	$.Redactor.modules = ['air', 'autosave', 'block', 'buffer', 'build', 'button', 'caret', 'clean', 'code', 'core', 'detect', 'dropdown',
18215						  'events', 'file', 'focus', 'image', 'indent', 'inline', 'insert', 'keydown', 'keyup',
18216						  'lang', 'line', 'link', 'linkify', 'list', 'marker', 'modal', 'observe', 'offset', 'paragraphize', 'paste', 'placeholder',
18217						  'progress', 'selection', 'shortcuts', 'storage', 'toolbar', 'upload', 'uploads3', 'utils',
18218
18219						  'browser' // deprecated
18220						  ];
18221
18222	$.Redactor.settings = {};
18223	$.Redactor.opts = {
18224
18225		// settings
18226		animation: false,
18227		lang: 'en',
18228		direction: 'ltr',
18229		spellcheck: true,
18230		overrideStyles: true,
18231		stylesClass: false,
18232		scrollTarget: document,
18233
18234		focus: false,
18235		focusEnd: false,
18236
18237		clickToEdit: false,
18238		structure: false,
18239
18240		tabindex: false,
18241
18242		minHeight: false, // string
18243		maxHeight: false, // string
18244
18245		maxWidth: false, // string
18246
18247		plugins: false, // array
18248		callbacks: {},
18249
18250		placeholder: false,
18251
18252		linkify: true,
18253		enterKey: true,
18254
18255		pastePlainText: false,
18256		pasteImages: true,
18257		pasteLinks: true,
18258		pasteBlockTags: ['pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'table', 'tbody', 'thead', 'tfoot', 'th', 'tr', 'td', 'ul', 'ol', 'li', 'blockquote', 'p', 'figure', 'figcaption'],
18259		pasteInlineTags: ['br', 'strong', 'ins', 'code', 'del', 'span', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small', 'b', 'u', 'em', 'i'],
18260
18261		preClass: false, // string
18262		preSpaces: 4, // or false
18263		tabAsSpaces: false, // true or number of spaces
18264		tabKey: true,
18265
18266		autosave: false, // false or url
18267		autosaveName: false,
18268		autosaveFields: false,
18269
18270		imageUpload: null,
18271		imageUploadParam: 'file',
18272		imageUploadFields: false,
18273		imageUploadForms: false,
18274        imageTag: 'figure',
18275        imageEditable: true,
18276		imageCaption: true,
18277
18278		imagePosition: false,
18279		imageResizable: false,
18280		imageFloatMargin: '10px',
18281
18282		dragImageUpload: true,
18283		multipleImageUpload: true,
18284		clipboardImageUpload: true,
18285
18286		fileUpload: null,
18287		fileUploadParam: 'file',
18288		fileUploadFields: false,
18289		fileUploadForms: false,
18290		dragFileUpload: true,
18291
18292		s3: false,
18293
18294        linkNewTab: false,
18295		linkTooltip: true,
18296		linkNofollow: false,
18297		linkSize: 30,
18298		linkValidation: true,
18299		pasteLinkTarget: false,
18300
18301		videoContainerClass: 'video-container',
18302
18303		toolbar: true,
18304		toolbarFixed: true,
18305		toolbarFixedTarget: document,
18306		toolbarFixedTopOffset: 0, // pixels
18307		toolbarExternal: false, // ID selector
18308		toolbarOverflow: false,
18309
18310		air: false,
18311		airWidth: false,
18312
18313		formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
18314		formattingAdd: false,
18315
18316		buttons: ['format', 'bold', 'italic', 'deleted', 'lists', 'image', 'file', 'link', 'horizontalrule'], // + 'horizontalrule', 'underline', 'ol', 'ul', 'indent', 'outdent'
18317        buttonsTextLabeled: false,
18318		buttonsHide: [],
18319		buttonsHideOnMobile: [],
18320
18321		script: true,
18322		removeNewlines: false,
18323		removeComments: true,
18324		replaceTags: {
18325			'b': 'strong',
18326			'i': 'em',
18327			'strike': 'del'
18328		},
18329
18330        keepStyleAttr: [], // tag name array
18331        keepInlineOnEnter: false,
18332
18333		// shortcuts
18334		shortcuts: {
18335			'ctrl+shift+m, meta+shift+m': { func: 'inline.removeFormat' },
18336			'ctrl+b, meta+b': { func: 'inline.format', params: ['bold'] },
18337			'ctrl+i, meta+i': { func: 'inline.format', params: ['italic'] },
18338			'ctrl+h, meta+h': { func: 'inline.format', params: ['superscript'] },
18339			'ctrl+l, meta+l': { func: 'inline.format', params: ['subscript'] },
18340			'ctrl+k, meta+k': { func: 'link.show' },
18341			'ctrl+shift+7':   { func: 'list.toggle', params: ['orderedlist'] },
18342			'ctrl+shift+8':   { func: 'list.toggle', params: ['unorderedlist'] }
18343		},
18344		shortcutsAdd: false,
18345
18346		activeButtons: ['deleted', 'italic', 'bold'],
18347		activeButtonsStates: {
18348			b: 'bold',
18349			strong: 'bold',
18350			i: 'italic',
18351			em: 'italic',
18352			del: 'deleted',
18353			strike: 'deleted'
18354		},
18355
18356		// private lang
18357		langs: {
18358			en: {
18359
18360				"format": "Format",
18361				"image": "Image",
18362				"file": "File",
18363				"link": "Link",
18364				"bold": "Bold",
18365				"italic": "Italic",
18366				"deleted": "Strikethrough",
18367				"underline": "Underline",
18368				"bold-abbr": "B",
18369				"italic-abbr": "I",
18370				"deleted-abbr": "S",
18371				"underline-abbr": "U",
18372				"lists": "Lists",
18373				"link-insert": "Insert link",
18374				"link-edit": "Edit link",
18375        "link-as-button": "Style link as button",
18376        "link-in-new-tab": "Open link in new tab",
18377				"unlink": "Unlink",
18378				"cancel": "Cancel",
18379				"close": "Close",
18380				"insert": "Insert",
18381				"save": "Save",
18382				"delete": "Delete",
18383				"text": "Text",
18384				"edit": "Edit",
18385				"title": "Title",
18386				"paragraph": "Normal text",
18387				"quote": "Quote",
18388				"code": "Code",
18389				"heading1": "Heading 1",
18390				"heading2": "Heading 2",
18391				"heading3": "Heading 3",
18392				"heading4": "Heading 4",
18393				"heading5": "Heading 5",
18394				"heading6": "Heading 6",
18395				"filename": "Name",
18396				"optional": "optional",
18397				"unorderedlist": "Unordered List",
18398				"orderedlist": "Ordered List",
18399				"outdent": "Outdent",
18400				"indent": "Indent",
18401				"horizontalrule": "Line",
18402				"upload-label": "Drop file here or ",
18403				"caption": "Caption",
18404
18405				"bulletslist": "Bullets",
18406				"numberslist": "Numbers",
18407
18408				"image-position": "Position",
18409				"none": "None",
18410				"left": "Left",
18411				"right": "Right",
18412				"center": "Center",
18413
18414				"accessibility-help-label": "Rich text editor"
18415			}
18416		},
18417
18418		// private
18419		type: 'textarea', // textarea, div, inline, pre
18420		inline: false,
18421		inlineTags: ['a', 'span', 'strong', 'strike', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'],
18422		blockTags: ['pre', 'ul', 'ol', 'li', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',  'dl', 'dt', 'dd', 'div', 'td', 'blockquote', 'output', 'figcaption', 'figure', 'address', 'section', 'header', 'footer', 'aside', 'article', 'iframe'],
18423		paragraphize: true,
18424		paragraphizeBlocks: ['table', 'div', 'pre', 'form', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'blockquote', 'figcaption',
18425							'address', 'section', 'header', 'footer', 'aside', 'article', 'object', 'style', 'script', 'iframe', 'select', 'input', 'textarea',
18426							'button', 'option', 'map', 'area', 'math', 'hr', 'fieldset', 'legend', 'hgroup', 'nav', 'figure', 'details', 'menu', 'summary', 'p'],
18427		emptyHtml: '<p>&#x200b;</p>',
18428		invisibleSpace: '&#x200b;',
18429		emptyHtmlRendered: $('').html('​').html(),
18430		imageTypes: ['image/png', 'image/jpeg', 'image/gif'],
18431		userAgent: navigator.userAgent.toLowerCase(),
18432		observe: {
18433			dropdowns: []
18434		},
18435		regexps: {
18436			linkyoutube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
18437			linkvimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
18438			linkimage: /((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/ig,
18439			url: /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/ig
18440		}
18441
18442	};
18443
18444	// Functionality
18445	Redactor.fn = $.Redactor.prototype = {
18446
18447		keyCode: {
18448			BACKSPACE: 8,
18449			DELETE: 46,
18450			UP: 38,
18451			DOWN: 40,
18452			ENTER: 13,
18453			SPACE: 32,
18454			ESC: 27,
18455			TAB: 9,
18456			CTRL: 17,
18457			META: 91,
18458			SHIFT: 16,
18459			ALT: 18,
18460			RIGHT: 39,
18461			LEFT: 37,
18462			LEFT_WIN: 91
18463		},
18464
18465		// =init
18466		init: function(el, options)
18467		{
18468			this.$element = $(el);
18469			this.uuid = uuid++;
18470			this.sBuffer = [];
18471            this.sRebuffer = [];
18472
18473			this.loadOptions(options);
18474			this.loadModules();
18475
18476			// click to edit
18477			if (this.opts.clickToEdit && !this.$element.hasClass('redactor-click-to-edit'))
18478			{
18479				return this.loadToEdit(options);
18480			}
18481			else if (this.$element.hasClass('redactor-click-to-edit'))
18482			{
18483				this.$element.removeClass('redactor-click-to-edit');
18484			}
18485
18486			// block & inline test tag regexp
18487			this.reIsBlock = new RegExp('^(' + this.opts.blockTags.join('|' ).toUpperCase() + ')$', 'i');
18488			this.reIsInline = new RegExp('^(' + this.opts.inlineTags.join('|' ).toUpperCase() + ')$', 'i');
18489
18490			// set up drag upload
18491			this.opts.dragImageUpload = (this.opts.imageUpload === null) ? false : this.opts.dragImageUpload;
18492			this.opts.dragFileUpload = (this.opts.fileUpload === null) ? false : this.opts.dragFileUpload;
18493
18494			// formatting storage
18495			this.formatting = {};
18496
18497			// load lang
18498			this.lang.load();
18499
18500			// extend shortcuts
18501			$.extend(this.opts.shortcuts, this.opts.shortcutsAdd);
18502
18503			// set editor
18504			this.$editor = this.$element;
18505
18506			// detect type of editor
18507			this.detectType();
18508
18509			// start callback
18510			this.core.callback('start');
18511			this.core.callback('startToEdit');
18512
18513			// build
18514			this.start = true;
18515			this.build.start();
18516
18517		},
18518		detectType: function()
18519		{
18520			if (this.build.isInline() || this.opts.inline)
18521			{
18522				this.opts.type = 'inline';
18523			}
18524			else if (this.build.isTag('DIV'))
18525			{
18526				this.opts.type = 'div';
18527			}
18528			else if (this.build.isTag('PRE'))
18529			{
18530				this.opts.type = 'pre';
18531			}
18532		},
18533		loadToEdit: function(options)
18534		{
18535
18536			this.$element.on('click.redactor-click-to-edit', $.proxy(function()
18537			{
18538				this.initToEdit(options);
18539
18540			}, this));
18541
18542			this.$element.addClass('redactor-click-to-edit');
18543
18544			return;
18545		},
18546		initToEdit: function(options)
18547		{
18548			$.extend(options.callbacks,  {
18549				startToEdit: function()
18550				{
18551					this.insert.node(this.marker.get(), false);
18552				},
18553				initToEdit: function()
18554				{
18555					this.selection.restore();
18556					this.clickToCancelStorage = this.code.get();
18557
18558					// cancel
18559					$(this.opts.clickToCancel).off('.redactor-click-to-edit');
18560					$(this.opts.clickToCancel).show().on('click.redactor-click-to-edit', $.proxy(function(e)
18561					{
18562						e.preventDefault();
18563
18564						this.core.destroy();
18565						this.events.syncFire = false;
18566						this.$element.html(this.clickToCancelStorage);
18567						this.core.callback('cancel', this.clickToCancelStorage);
18568						this.events.syncFire = true;
18569						this.clickToCancelStorage = '';
18570						$(this.opts.clickToCancel).hide();
18571						$(this.opts.clickToSave).hide();
18572
18573						this.$element.on('click.redactor-click-to-edit', $.proxy(function()
18574						{
18575							this.initToEdit(options);
18576						}, this));
18577
18578						this.$element.addClass('redactor-click-to-edit');
18579
18580					}, this));
18581
18582					// save
18583					$(this.opts.clickToSave).off('.redactor-click-to-edit');
18584					$(this.opts.clickToSave).show().on('click.redactor-click-to-edit', $.proxy(function(e)
18585					{
18586						e.preventDefault();
18587
18588						this.core.destroy();
18589						this.core.callback('save', this.code.get());
18590						$(this.opts.clickToCancel).hide();
18591						$(this.opts.clickToSave).hide();
18592						this.$element.on('click.redactor-click-to-edit', $.proxy(function()
18593						{
18594							this.initToEdit(options);
18595						}, this));
18596						this.$element.addClass('redactor-click-to-edit');
18597
18598					}, this));
18599				}
18600
18601			});
18602
18603			this.$element.redactor(options);
18604			this.$element.off('.redactor-click-to-edit');
18605
18606		},
18607		loadOptions: function(options)
18608		{
18609			var settings = {};
18610
18611			// check namespace
18612			if (typeof $.Redactor.settings.namespace !== 'undefined')
18613			{
18614				 if (this.$element.hasClass($.Redactor.settings.namespace))
18615				 {
18616					 settings = $.Redactor.settings;
18617				 }
18618			}
18619			else
18620			{
18621				settings = $.Redactor.settings;
18622			}
18623
18624			this.opts = $.extend(
18625				{},
18626				$.Redactor.opts,
18627				this.$element.data(),
18628				options
18629			);
18630
18631			this.opts = $.extend({}, this.opts, settings);
18632
18633		},
18634		getModuleMethods: function(object)
18635		{
18636			return Object.getOwnPropertyNames(object).filter(function(property)
18637			{
18638				return typeof object[property] === 'function';
18639			});
18640		},
18641		loadModules: function()
18642		{
18643			var len = $.Redactor.modules.length;
18644			for (var i = 0; i < len; i++)
18645			{
18646				this.bindModuleMethods($.Redactor.modules[i]);
18647			}
18648		},
18649		bindModuleMethods: function(module)
18650		{
18651			if (typeof this[module] === 'undefined')
18652			{
18653				return;
18654			}
18655
18656			// init module
18657			this[module] = this[module]();
18658
18659			var methods = this.getModuleMethods(this[module]);
18660			var len = methods.length;
18661
18662			// bind methods
18663			for (var z = 0; z < len; z++)
18664			{
18665				this[module][methods[z]] = this[module][methods[z]].bind(this);
18666			}
18667		},
18668
18669		// =air
18670		air: function()
18671		{
18672			return {
18673				enabled: false,
18674				collapsed: function()
18675				{
18676					if (this.opts.air)
18677					{
18678						this.selection.get().collapseToStart();
18679					}
18680				},
18681				collapsedEnd: function()
18682				{
18683					if (this.opts.air)
18684					{
18685						this.selection.get().collapseToEnd();
18686					}
18687				},
18688				build: function()
18689				{
18690					if (this.detect.isMobile())
18691					{
18692						return;
18693					}
18694
18695					this.button.hideButtons();
18696					this.button.hideButtonsOnMobile();
18697
18698					if (this.opts.buttons.length === 0)
18699					{
18700						return;
18701					}
18702
18703					this.$air = this.air.createContainer();
18704
18705					if (this.opts.airWidth !== false)
18706					{
18707						this.$air.css('width', this.opts.airWidth);
18708					}
18709
18710					this.air.append();
18711					this.button.$toolbar = this.$air;
18712					this.button.setFormatting();
18713					this.button.load(this.$air);
18714
18715					this.core.editor().on('mouseup.redactor', this, $.proxy(function(e)
18716					{
18717						if (this.selection.text() !== '')
18718						{
18719							this.air.show(e);
18720						}
18721					}, this));
18722
18723				},
18724				append: function()
18725				{
18726					this.$air.appendTo('body');
18727				},
18728				createContainer: function()
18729				{
18730					return $('<ul>').addClass('redactor-air').attr({ 'id': 'redactor-air-' + this.uuid, 'role': 'toolbar' }).hide();
18731				},
18732				show: function (e)
18733				{
18734					//this.marker.remove();
18735					this.selection.saveInstant();
18736					//this.selection.restore(false);
18737
18738					$('.redactor-air').hide();
18739
18740					var leftFix = 0;
18741					var width = this.$air.innerWidth();
18742
18743					if ($(window).width() < (e.clientX + width))
18744					{
18745						leftFix = 200;
18746					}
18747
18748					this.$air.css({
18749						left: (e.clientX - leftFix) + 'px',
18750						top: (e.clientY + 10 + $(document).scrollTop()) + 'px'
18751					}).show();
18752
18753					this.air.enabled = true;
18754					this.air.bindHide();
18755				},
18756				bindHide: function()
18757				{
18758					$(document).on('mousedown.redactor-air.' + this.uuid, $.proxy(function(e)
18759					{
18760						var dropdown = $(e.target).closest('.redactor-dropdown').length;
18761
18762						if ($(e.target).closest(this.$air).length === 0 && dropdown === 0)
18763						{
18764							var hide = this.air.hide(e);
18765							if (hide !== false)
18766							{
18767								this.marker.remove();
18768							}
18769						}
18770
18771					}, this)).on('keydown.redactor-air.' + this.uuid, $.proxy(function(e)
18772					{
18773						var key = e.which;
18774						if ((!this.utils.isRedactorParent(e.target) && !$(e.target).hasClass('redactor-in')) || $(e.target).closest('#redactor-modal').length !== 0)
18775						{
18776							return;
18777						}
18778
18779						if (key === this.keyCode.ESC)
18780						{
18781							this.selection.get().collapseToStart();
18782							//this.marker.remove();
18783						}
18784						else if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
18785						{
18786							var sel = this.selection.get();
18787							var range = this.selection.range(sel);
18788							range.deleteContents();
18789							//this.marker.remove();
18790						}
18791						else if (key === this.keyCode.ENTER)
18792						{
18793							this.selection.get().collapseToEnd();
18794							//this.marker.remove();
18795						}
18796
18797						if (this.air.enabled)
18798						{
18799							this.air.hide(e);
18800						}
18801						else
18802						{
18803							this.selection.get().collapseToStart();
18804							//this.marker.remove();
18805						}
18806
18807
18808					}, this));
18809				},
18810				hide: function(e)
18811				{
18812					var ctrl = e.ctrlKey || e.metaKey || (e.shiftKey && e.altKey);
18813					if (ctrl)
18814					{
18815						return false;
18816					}
18817
18818					this.button.setInactiveAll();
18819					this.$air.fadeOut(100);
18820					this.air.enabled = false;
18821					$(document).off('mousedown.redactor-air.' + this.uuid);
18822					$(document).off('keydown.redactor-air.' + this.uuid);
18823
18824				}
18825			};
18826		},
18827
18828		// =autosave
18829		autosave: function()
18830		{
18831			return {
18832				enabled: false,
18833				html: false,
18834				init: function()
18835				{
18836					if (!this.opts.autosave)
18837					{
18838						return;
18839					}
18840
18841					this.autosave.enabled = true;
18842					this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name');
18843
18844				},
18845				is: function()
18846				{
18847					return this.autosave.enabled;
18848				},
18849				send: function()
18850				{
18851					if (!this.opts.autosave)
18852					{
18853						return;
18854					}
18855
18856					this.autosave.source = this.code.get();
18857
18858					if (this.autosave.html === this.autosave.source)
18859					{
18860						return;
18861					}
18862
18863					// data
18864					var data = {};
18865					data.name = this.autosave.name;
18866					data[this.autosave.name] = this.autosave.source;
18867					data = this.autosave.getHiddenFields(data);
18868
18869					// ajax
18870					var jsxhr = $.ajax({
18871						url: this.opts.autosave,
18872						type: 'post',
18873						data: data
18874					});
18875
18876					jsxhr.done(this.autosave.success);
18877				},
18878				getHiddenFields: function(data)
18879				{
18880					if (this.opts.autosaveFields === false || typeof this.opts.autosaveFields !== 'object')
18881					{
18882						return data;
18883					}
18884
18885					$.each(this.opts.autosaveFields, $.proxy(function(k, v)
18886					{
18887						if (v !== null && v.toString().indexOf('#') === 0)
18888						{
18889							v = $(v).val();
18890						}
18891
18892						data[k] = v;
18893
18894					}, this));
18895
18896					return data;
18897
18898				},
18899				success: function(data)
18900				{
18901					var json;
18902					try
18903					{
18904						json = JSON.parse(data);
18905					}
18906					catch(e)
18907					{
18908						//data has already been parsed
18909						json = data;
18910					}
18911
18912					var callbackName = (typeof json.error === 'undefined') ? 'autosave' :  'autosaveError';
18913
18914					this.core.callback(callbackName, this.autosave.name, json);
18915					this.autosave.html = this.autosave.source;
18916				},
18917				disable: function()
18918				{
18919					this.autosave.enabled = false;
18920
18921					clearInterval(this.autosaveTimeout);
18922				}
18923			};
18924		},
18925
18926		// =block
18927		block: function()
18928		{
18929			return {
18930				format: function(tag, attr, value, type)
18931				{
18932					tag = (tag === 'quote') ? 'blockquote' : tag;
18933
18934					this.block.tags = ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'figure'];
18935					if ($.inArray(tag, this.block.tags) === -1)
18936					{
18937						return;
18938					}
18939
18940					if (tag === 'p' && typeof attr === 'undefined')
18941					{
18942						// remove all
18943						attr = 'class';
18944					}
18945
18946					this.placeholder.hide();
18947					this.buffer.set();
18948
18949					return (this.utils.isCollapsed()) ? this.block.formatCollapsed(tag, attr, value, type) : this.block.formatUncollapsed(tag, attr, value, type);
18950				},
18951				formatCollapsed: function(tag, attr, value, type)
18952				{
18953					this.selection.save();
18954
18955					var block = this.selection.block();
18956					var currentTag = block.tagName.toLowerCase();
18957					if ($.inArray(currentTag, this.block.tags) === -1)
18958					{
18959						this.selection.restore();
18960						return;
18961					}
18962
18963                    var clearAllAttrs = false;
18964					if (currentTag === tag && attr === undefined)
18965					{
18966						tag = 'p';
18967						clearAllAttrs = true;
18968					}
18969
18970
18971                    if (clearAllAttrs)
18972                    {
18973                        this.block.removeAllClass();
18974                        this.block.removeAllAttr();
18975                    }
18976
18977
18978                    var replaced;
18979                    if (currentTag === 'blockquote' && this.utils.isEndOfElement(block))
18980                    {
18981                        this.marker.remove();
18982
18983                        replaced = document.createElement('p');
18984                        replaced.innerHTML = this.opts.invisibleSpace;
18985
18986                        $(block).after(replaced);
18987                        this.caret.start(replaced);
18988                        var $last = $(block).children().last();
18989
18990                        if ($last.length !== 0 && $last[0].tagName === 'BR')
18991                        {
18992                            $last.remove();
18993                        }
18994                    }
18995                    else
18996                    {
18997					    replaced = this.utils.replaceToTag(block, tag);
18998                    }
18999
19000
19001					if (typeof attr === 'object')
19002					{
19003						type = value;
19004						for (var key in attr)
19005						{
19006							replaced = this.block.setAttr(replaced, key, attr[key], type);
19007						}
19008					}
19009					else
19010					{
19011						replaced = this.block.setAttr(replaced, attr, value, type);
19012					}
19013
19014
19015					// trim pre
19016					if (tag === 'pre' && replaced.length === 1)
19017					{
19018						$(replaced).html($.trim($(replaced).html()));
19019					}
19020
19021					this.selection.restore();
19022					this.block.removeInlineTags(replaced);
19023
19024					return replaced;
19025				},
19026				formatUncollapsed: function(tag, attr, value, type)
19027				{
19028                    this.selection.save();
19029
19030					var replaced = [];
19031					var blocks = this.selection.blocks();
19032
19033                    if (blocks[0] && ($(blocks[0]).hasClass('redactor-in') || $(blocks[0]).hasClass('redactor-box')))
19034                    {
19035                        blocks = this.core.editor().find(this.opts.blockTags.join(', '));
19036					}
19037
19038					var len = blocks.length;
19039					for (var i = 0; i < len; i++)
19040					{
19041						var currentTag = blocks[i].tagName.toLowerCase();
19042						if ($.inArray(currentTag, this.block.tags) !== -1 && currentTag !== 'figure')
19043						{
19044							var block = this.utils.replaceToTag(blocks[i], tag);
19045
19046							if (typeof attr === 'object')
19047							{
19048								type = value;
19049								for (var key in attr)
19050								{
19051									block = this.block.setAttr(block, key, attr[key], type);
19052								}
19053							}
19054							else
19055							{
19056								block = this.block.setAttr(block, attr, value, type);
19057							}
19058
19059							replaced.push(block);
19060							this.block.removeInlineTags(block);
19061						}
19062					}
19063
19064					this.selection.restore();
19065
19066					// combine pre
19067					if (tag === 'pre' && replaced.length !== 0)
19068					{
19069						var first = replaced[0];
19070						$.each(replaced, function(i,s)
19071						{
19072							if (i !== 0)
19073							{
19074								$(first).append("\n" + $.trim(s.html()));
19075								$(s).remove();
19076							}
19077						});
19078
19079						replaced = [];
19080						replaced.push(first);
19081					}
19082
19083					return replaced;
19084				},
19085				removeInlineTags: function(node)
19086				{
19087					node = node[0] || node;
19088
19089					var tags = this.opts.inlineTags;
19090					var blocks = ['PRE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
19091
19092					if ($.inArray(node.tagName, blocks) === - 1)
19093					{
19094						return;
19095					}
19096
19097					if (node.tagName !== 'PRE')
19098					{
19099						var index = tags.indexOf('a');
19100						tags.splice(index, 1);
19101					}
19102
19103					$(node).find(tags.join(',')).not('.redactor-selection-marker').contents().unwrap();
19104				},
19105				setAttr: function(block, attr, value, type)
19106				{
19107					if (typeof attr === 'undefined')
19108					{
19109						return block;
19110					}
19111
19112					var func = (typeof type === 'undefined') ? 'replace' : type;
19113
19114					if (attr === 'class')
19115					{
19116						block = this.block[func + 'Class'](value, block);
19117					}
19118					else
19119					{
19120						if (func === 'remove')
19121						{
19122							block = this.block[func + 'Attr'](attr, block);
19123						}
19124						else if (func === 'removeAll')
19125						{
19126							block = this.block[func + 'Attr'](attr, block);
19127						}
19128						else
19129						{
19130							block = this.block[func + 'Attr'](attr, value, block);
19131						}
19132					}
19133
19134					return block;
19135
19136				},
19137				getBlocks: function(block)
19138				{
19139					block = (typeof block === 'undefined') ? this.selection.blocks() : block;
19140
19141    				if ($(block).hasClass('redactor-box'))
19142    				{
19143        				var blocks = [];
19144        				var nodes = this.core.editor().children();
19145        			    $.each(nodes, $.proxy(function(i,node)
19146    					{
19147    						if (this.utils.isBlock(node))
19148    						{
19149    							blocks.push(node);
19150    						}
19151
19152    					}, this));
19153
19154    					return blocks;
19155    				}
19156
19157    				return block
19158				},
19159				replaceClass: function(value, block)
19160				{
19161					return $(this.block.getBlocks(block)).removeAttr('class').addClass(value)[0];
19162				},
19163				toggleClass: function(value, block)
19164				{
19165					return $(this.block.getBlocks(block)).toggleClass(value)[0];
19166				},
19167				addClass: function(value, block)
19168				{
19169					return $(this.block.getBlocks(block)).addClass(value)[0];
19170				},
19171				removeClass: function(value, block)
19172				{
19173					return $(this.block.getBlocks(block)).removeClass(value)[0];
19174				},
19175				removeAllClass: function(block)
19176				{
19177					return $(this.block.getBlocks(block)).removeAttr('class')[0];
19178				},
19179				replaceAttr: function(attr, value, block)
19180				{
19181					block = this.block.removeAttr(attr, block);
19182
19183					return $(block).attr(attr, value)[0];
19184				},
19185				toggleAttr: function(attr, value, block)
19186				{
19187					block = this.block.getBlocks(block);
19188
19189					var self = this;
19190					var returned = [];
19191					$.each(block, function(i,s)
19192					{
19193						var $el = $(s);
19194						if ($el.attr(attr))
19195						{
19196							returned.push(self.block.removeAttr(attr, s));
19197						}
19198						else
19199						{
19200							returned.push(self.block.addAttr(attr, value, s));
19201						}
19202					});
19203
19204					return returned;
19205
19206				},
19207				addAttr: function(attr, value, block)
19208				{
19209					return $(this.block.getBlocks(block)).attr(attr, value)[0];
19210				},
19211				removeAttr: function(attr, block)
19212				{
19213					return $(this.block.getBlocks(block)).removeAttr(attr)[0];
19214				},
19215				removeAllAttr: function(block)
19216				{
19217					block = this.block.getBlocks(block);
19218
19219					var returned = [];
19220					$.each(block, function(i,s)
19221					{
19222						if (typeof s.attributes !== 'undefined')
19223                        {
19224    						while (s.attributes.length)
19225    						{
19226                                s.removeAttribute(s.attributes[0].name);
19227                            }
19228                        }
19229
19230						returned.push(s);
19231					});
19232
19233					return returned;
19234				}
19235			};
19236		},
19237
19238		// buffer
19239		buffer: function()
19240		{
19241			return {
19242				set: function(type)
19243				{
19244    				if (typeof type === 'undefined')
19245                    {
19246                        this.buffer.clear();
19247                    }
19248
19249					if (typeof type === 'undefined' || type === 'undo')
19250					{
19251						this.buffer.setUndo();
19252					}
19253					else
19254					{
19255						this.buffer.setRedo();
19256					}
19257				},
19258				setUndo: function()
19259				{
19260                    var saved = this.selection.saveInstant();
19261
19262					var last = this.sBuffer[this.sBuffer.length-1];
19263					var current = this.core.editor().html();
19264
19265					var save = (typeof last !== 'undefined' && (last[0] === current)) ? false : true;
19266    				if (save)
19267    				{
19268						this.sBuffer.push([current, saved]);
19269					}
19270
19271					//this.selection.restore();
19272				},
19273				setRedo: function()
19274				{
19275					var saved = this.selection.saveInstant();
19276					this.sRebuffer.push([this.core.editor().html(), saved]);
19277					//this.selection.restore();
19278				},
19279				add: function()
19280				{
19281					this.sBuffer.push([this.core.editor().html(), 0]);
19282				},
19283				undo: function()
19284				{
19285					if (this.sBuffer.length === 0)
19286					{
19287						return;
19288					}
19289
19290					var buffer = this.sBuffer.pop();
19291
19292					this.buffer.set('redo');
19293					this.core.editor().html(buffer[0]);
19294
19295					this.selection.restoreInstant(buffer[1]);
19296					this.selection.restore();
19297					this.observe.load();
19298				},
19299				redo: function()
19300				{
19301					if (this.sRebuffer.length === 0)
19302					{
19303						return;
19304					}
19305
19306					var buffer = this.sRebuffer.pop();
19307
19308					this.buffer.set('undo');
19309					this.core.editor().html(buffer[0]);
19310
19311					this.selection.restoreInstant(buffer[1]);
19312					this.selection.restore();
19313					this.observe.load();
19314				},
19315				clear: function()
19316				{
19317    				this.sRebuffer = [];
19318				}
19319			};
19320		},
19321
19322		// =build
19323		build: function()
19324		{
19325			return {
19326				start: function()
19327				{
19328					if (this.opts.type === 'inline')
19329					{
19330						this.opts.type = 'inline';
19331					}
19332					else if (this.opts.type === 'div')
19333					{
19334						// empty
19335						var html = $.trim(this.$editor.html());
19336						if (html === '')
19337						{
19338							this.$editor.html(this.opts.emptyHtml);
19339						}
19340
19341						this.build.buildTextarea();
19342					}
19343					else if (this.opts.type === 'textarea')
19344					{
19345						this.build.startTextarea();
19346					}
19347
19348					// set in
19349					this.build.setIn();
19350
19351					// set id
19352					this.build.setId();
19353
19354					// enable
19355					this.build.enableEditor();
19356
19357					// options
19358					this.build.setOptions();
19359
19360					// call
19361					this.build.callEditor();
19362
19363
19364				},
19365				createContainerBox: function()
19366				{
19367					this.$box = $('<div class="redactor-box" role="application" />');
19368				},
19369				setIn: function()
19370				{
19371					this.core.editor().addClass('redactor-in');
19372				},
19373				setId: function()
19374				{
19375					var id = (this.opts.type === 'textarea') ? 'redactor-uuid-' + this.uuid : this.$element.attr('id');
19376
19377					this.core.editor().attr('id', (typeof id === 'undefined') ? 'redactor-uuid-' + this.uuid : id);
19378				},
19379				getName: function()
19380				{
19381					var name = this.$element.attr('name');
19382
19383					return (typeof name === 'undefined') ? 'content-' + this.uuid : n
19383ame;
19384				},
19385				buildTextarea: function()
19386				{
19387    				this.$textarea = $('<textarea>');
19388    				this.$textarea.attr('name', this.build.getName());
19389    				this.$textarea.hide();
19390    				this.$element.after(this.$textarea);
19391
19392    				this.build.setStartAttrs();
19393				},
19394				loadFromTextarea: function()
19395				{
19396					this.$editor = $('<div />');
19397
19398					// textarea
19399					this.$textarea = this.$element;
19400					this.$element.attr('name', this.build.getName());
19401
19402					// place
19403					this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element);
19404
19405                    this.build.setStartAttrs();
19406
19407                    // styles
19408                    this.$editor.addClass('redactor-layer');
19409                    if (this.opts.overrideStyles) this.$editor.addClass('redactor-styles');
19410
19411					this.$element.hide();
19412
19413					this.$box.prepend('<span class="redactor-voice-label" id="redactor-voice-' + this.uuid +'" aria-hidden="false">' + this.lang.get('accessibility-help-label') + '</span>');
19414
19415				},
19416				setStartAttrs: function()
19417				{
19418                    this.$editor.attr({ 'aria-labelledby': 'redactor-voice-' + this.uuid, 'role': 'presentation' });
19419				},
19420				startTextarea: function()
19421				{
19422					this.build.createContainerBox();
19423
19424					// load
19425					this.build.loadFromTextarea();
19426
19427					// set code
19428					this.code.start(this.core.textarea().val());
19429
19430					// set value
19431					this.core.textarea().val(this.clean.onSync(this.$editor.html()));
19432				},
19433				isTag: function(tag)
19434				{
19435					return (this.$element[0].tagName === tag);
19436				},
19437				isInline: function()
19438				{
19439					return (!this.build.isTag('TEXTAREA') && !this.build.isTag('DIV') && !this.build.isTag('PRE'));
19440				},
19441				enableEditor: function()
19442				{
19443					this.core.editor().attr({ 'contenteditable': true });
19444				},
19445				setOptions: function()
19446				{
19447					// inline
19448					if (this.opts.type === 'inline')
19449					{
19450						this.opts.enterKey = false;
19451					}
19452
19453					// inline & pre
19454					if (this.opts.type === 'inline' || this.opts.type === 'pre')
19455					{
19456						this.opts.toolbarMobile = false;
19457						this.opts.toolbar = false;
19458						this.opts.air = false;
19459						this.opts.linkify = false;
19460
19461					}
19462
19463					// spellcheck
19464					this.core.editor().attr('spellcheck', this.opts.spellcheck);
19465
19466					// structure
19467					if (this.opts.structure)
19468					{
19469						this.core.editor().addClass('redactor-structure');
19470					}
19471
19472					// styles class
19473					if (this.opts.stylesClass)
19474					{
19475						this.core.editor().addClass(this.opts.stylesClass);
19476					}
19477
19478					// options sets only in textarea mode
19479					if (this.opts.type !== 'textarea')
19480					{
19481						return;
19482					}
19483
19484					// direction
19485					this.core.box().attr('dir', this.opts.direction);
19486					this.core.editor().attr('dir', this.opts.direction);
19487
19488					// tabindex
19489					if (this.opts.tabindex)
19490					{
19491						this.core.editor().attr('tabindex', this.opts.tabindex);
19492					}
19493
19494					// min height
19495					if (this.opts.minHeight)
19496					{
19497						this.core.editor().css('min-height', this.opts.minHeight);
19498					}
19499					else
19500					{
19501						this.core.editor().css('min-height', '40px');
19502					}
19503
19504					// max height
19505					if (this.opts.maxHeight)
19506					{
19507						this.core.editor().css('max-height', this.opts.maxHeight);
19508					}
19509
19510					// max width
19511					if (this.opts.maxWidth)
19512					{
19513						this.core.editor().css({ 'max-width': this.opts.maxWidth, 'margin': 'auto' });
19514					}
19515
19516				},
19517				callEditor: function()
19518				{
19519					this.build.disableBrowsersEditing();
19520
19521					this.events.init();
19522					this.build.setHelpers();
19523
19524					// init buttons
19525					if (this.opts.toolbar || this.opts.air)
19526					{
19527						this.toolbarsButtons = this.button.init();
19528					}
19529
19530					// load toolbar
19531					if (this.opts.air)
19532					{
19533						this.air.build();
19534					}
19535					else if (this.opts.toolbar)
19536					{
19537						this.toolbar.build();
19538					}
19539
19540					if (this.detect.isMobile() && this.opts.toolbarMobile && this.opts.air)
19541					{
19542						this.opts.toolbar = true;
19543						this.toolbar.build();
19544					}
19545
19546					// observe dropdowns
19547					if (this.opts.air || this.opts.toolbar)
19548					{
19549						this.core.editor().on('mouseup.redactor-observe.' + this.uuid + ' keyup.redactor-observe.' + this.uuid + ' focus.redactor-observe.' + this.uuid + ' touchstart.redactor-observe.' + this.uuid, $.proxy(this.observe.toolbar, this));
19550						this.core.element().on('blur.callback.redactor', $.proxy(function()
19551						{
19552							this.button.setInactiveAll();
19553
19554						}, this));
19555					}
19556
19557					// modal templates init
19558					this.modal.templates();
19559
19560					// plugins
19561					this.build.plugins();
19562
19563					// autosave
19564					this.autosave.init();
19565
19566					// sync code
19567					this.code.html = this.code.cleaned(this.core.editor().html());
19568
19569					// init callback
19570					this.core.callback('init');
19571					this.core.callback('initToEdit');
19572
19573					// get images & files list
19574					this.storage.observe();
19575
19576					// started
19577					this.start = false;
19578
19579				},
19580				setHelpers: function()
19581				{
19582					// linkify
19583					if (this.opts.linkify)
19584					{
19585						this.linkify.format();
19586					}
19587
19588					// placeholder
19589					this.placeholder.init();
19590
19591					// focus
19592					if (this.opts.focus)
19593					{
19594						setTimeout(this.focus.start, 100);
19595					}
19596					else if (this.opts.focusEnd)
19597					{
19598						setTimeout(this.focus.end, 100);
19599					}
19600
19601				},
19602				disableBrowsersEditing: function()
19603				{
19604					try {
19605						// FF fix
19606						document.execCommand('enableObjectResizing', false, false);
19607						document.execCommand('enableInlineTableEditing', false, false);
19608						// IE prevent converting links
19609						document.execCommand("AutoUrlDetect", false, false);
19610					} catch (e) {}
19611				},
19612				plugins: function()
19613				{
19614					if (!this.opts.plugins)
19615					{
19616						return;
19617					}
19618
19619					$.each(this.opts.plugins, $.proxy(function(i, s)
19620					{
19621						var func = (typeof RedactorPlugins !== 'undefined' && typeof RedactorPlugins[s] !== 'undefined') ? RedactorPlugins : Redactor.fn;
19622
19623						if (!$.isFunction(func[s]))
19624						{
19625							return;
19626						}
19627
19628						this[s] = func[s]();
19629
19630						// get methods
19631						var methods = this.getModuleMethods(this[s]);
19632						var len = methods.length;
19633
19634						// bind methods
19635						for (var z = 0; z < len; z++)
19636						{
19637							this[s][methods[z]] = this[s][methods[z]].bind(this);
19638						}
19639
19640						// append lang
19641						if (typeof this[s].langs !== 'undefined')
19642						{
19643							var lang = {};
19644							if (typeof this[s].langs[this.opts.lang] !== 'undefined')
19645							{
19646								lang = this[s].langs[this.opts.lang];
19647							}
19648							else if (typeof this[s].langs[this.opts.lang] === 'undefined' && typeof this[s].langs.en !== 'undefined')
19649							{
19650								lang = this[s].langs.en;
19651							}
19652
19653							// extend
19654							var self = this;
19655							$.each(lang, function(i,s)
19656							{
19657								if (typeof self.opts.curLang[i] === 'undefined')
19658								{
19659									self.opts.curLang[i] = s;
19660								}
19661							});
19662						}
19663
19664						// init
19665						if ($.isFunction(this[s].init))
19666						{
19667							this[s].init();
19668						}
19669
19670
19671					}, this));
19672
19673				}
19674			};
19675		},
19676
19677		// =button
19678		button: function()
19679		{
19680			return {
19681				toolbar: function()
19682				{
19683					return (typeof this.button.$toolbar === 'undefined' || !this.button.$toolbar) ? this.$toolbar : this.button.$toolbar;
19684				},
19685				init: function()
19686				{
19687					return {
19688						format:
19689						{
19690							title: this.lang.get('format'),
19691							icon: true,
19692							dropdown:
19693							{
19694								p:
19695								{
19696									title: this.lang.get('paragraph'),
19697									func: 'block.format'
19698								},
19699								blockquote:
19700								{
19701									title: this.lang.get('quote'),
19702									func: 'block.format'
19703								},
19704								pre:
19705								{
19706									title: this.lang.get('code'),
19707									func: 'block.format'
19708								},
19709								h1:
19710								{
19711									title: this.lang.get('heading1'),
19712									func: 'block.format'
19713								},
19714								h2:
19715								{
19716									title: this.lang.get('heading2'),
19717									func: 'block.format'
19718								},
19719								h3:
19720								{
19721									title: this.lang.get('heading3'),
19722									func: 'block.format'
19723								},
19724								h4:
19725								{
19726									title: this.lang.get('heading4'),
19727									func: 'block.format'
19728								},
19729								h5:
19730								{
19731									title: this.lang.get('heading5'),
19732									func: 'block.format'
19733								},
19734								h6:
19735								{
19736									title: this.lang.get('heading6'),
19737									func: 'block.format'
19738								}
19739							}
19740						},
19741						bold:
19742						{
19743							title: this.lang.get('bold-abbr'),
19744							icon: true,
19745							label: this.lang.get('bold'),
19746							func: 'inline.format'
19747						},
19748						italic:
19749						{
19750							title: this.lang.get('italic-abbr'),
19751							icon: true,
19752							label: this.lang.get('italic'),
19753							func: 'inline.format'
19754						},
19755						deleted:
19756						{
19757							title: this.lang.get('deleted-abbr'),
19758							icon: true,
19759							label: this.lang.get('deleted'),
19760							func: 'inline.format'
19761						},
19762						underline:
19763						{
19764							title: this.lang.get('underline-abbr'),
19765							icon: true,
19766							label: this.lang.get('underline'),
19767							func: 'inline.format'
19768						},
19769						lists:
19770						{
19771							title: this.lang.get('lists'),
19772							icon: true,
19773							dropdown:
19774							{
19775								unorderedlist:
19776								{
19777									title: '&bull; ' + this.lang.get('unorderedlist'),
19778									func: 'list.toggle'
19779								},
19780								orderedlist:
19781								{
19782									title: '1. ' + this.lang.get('orderedlist'),
19783									func: 'list.toggle'
19784								},
19785								outdent:
19786								{
19787									title: '< ' + this.lang.get('outdent'),
19788									func: 'indent.decrease',
19789									observe: {
19790										element: 'li',
19791										out: {
19792											attr: {
19793												'class': 'redactor-dropdown-link-inactive',
19794												'aria-disabled': true
19795											}
19796										}
19797									}
19798								},
19799								indent:
19800								{
19801									title: '> ' + this.lang.get('indent'),
19802									func: 'indent.increase',
19803									observe: {
19804										element: 'li',
19805										out: {
19806											attr: {
19807												'class': 'redactor-dropdown-link-inactive',
19808												'aria-disabled': true
19809											}
19810										}
19811									}
19812								}
19813							}
19814						},
19815						ul:
19816						{
19817							title: '&bull; ' + this.lang.get('bulletslist'),
19818							icon: true,
19819							func: 'list.toggle'
19820						},
19821						ol:
19822						{
19823							title: '1. ' + this.lang.get('numberslist'),
19824							icon: true,
19825							func: 'list.toggle'
19826						},
19827						outdent:
19828						{
19829							title: this.lang.get('outdent'),
19830							icon: true,
19831							func: 'indent.decrease'
19832						},
19833						indent:
19834						{
19835							title: this.lang.get('indent'),
19836							icon: true,
19837							func: 'indent.increase'
19838						},
19839						image:
19840						{
19841							title: this.lang.get('image'),
19842							icon: true,
19843							func: 'image.show'
19844						},
19845						file:
19846						{
19847							title: this.lang.get('file'),
19848							icon: true,
19849							func: 'file.show'
19850						},
19851						link:
19852						{
19853							title: this.lang.get('link'),
19854							icon: true,
19855							dropdown:
19856							{
19857								link:
19858								{
19859									title: this.lang.get('link-insert'),
19860									func: 'link.show',
19861									observe: {
19862										element: 'a',
19863										'in': {
19864											title: this.lang.get('link-edit')
19865										},
19866										out: {
19867											title: this.lang.get('link-insert')
19868										}
19869									}
19870								},
19871								unlink:
19872								{
19873									title: this.lang.get('unlink'),
19874									func: 'link.unlink',
19875									observe: {
19876										element: 'a',
19877										out: {
19878											attr: {
19879												'class': 'redactor-dropdown-link-inactive',
19880												'aria-disabled': true
19881											}
19882										}
19883									}
19884								}
19885							}
19886						},
19887						horizontalrule:
19888						{
19889							title: this.lang.get('horizontalrule'),
19890							icon: true,
19891							func: 'line.insert'
19892						}
19893					};
19894				},
19895				setFormatting: function()
19896				{
19897					$.each(this.toolbarsButtons.format.dropdown, $.proxy(function (i, s)
19898					{
19899						if ($.inArray(i, this.opts.formatting) === -1)
19900						{
19901							delete this.toolbarsButtons.format.dropdown[i];
19902						}
19903
19904					}, this));
19905
19906				},
19907				hideButtons: function()
19908				{
19909					if (this.opts.buttonsHide.length !== 0)
19910					{
19911						this.button.hideButtonsSlicer(this.opts.buttonsHide);
19912					}
19913				},
19914				hideButtonsOnMobile: function()
19915				{
19916					if (this.detect.isMobile() && this.opts.buttonsHideOnMobile.length !== 0)
19917					{
19918						this.button.hideButtonsSlicer(this.opts.buttonsHideOnMobile);
19919					}
19920				},
19921				hideButtonsSlicer: function(buttons)
19922				{
19923					$.each(buttons, $.proxy(function(i, s)
19924					{
19925						var index = this.opts.buttons.indexOf(s);
19926						if (index !== -1)
19927						{
19928						    this.opts.buttons.splice(index, 1);
19929						}
19930
19931					}, this));
19932				},
19933				load: function($toolbar)
19934				{
19935					this.button.buttons = [];
19936
19937					$.each(this.opts.buttons, $.proxy(function(i, btnName)
19938					{
19939						if (!this.toolbarsButtons[btnName]
19940							|| (btnName === 'file' && !this.file.is())
19941							|| (btnName === 'image' && !this.image.is()))
19942						{
19943							return;
19944						}
19945
19946						$toolbar.append($('<li>').append(this.button.build(btnName, this.toolbarsButtons[btnName])));
19947
19948					}, this));
19949				},
19950				buildButtonTooltip: function($btn, title)
19951				{
19952    				if (typeof this.button.toolbar() === 'undefined') return;
19953    				if (this.opts.air || this.detect.isMobile()) return;
19954
19955                    var $tooltip = $('<span>');
19956				    $tooltip.addClass('re-button-tooltip');
19957				    $tooltip.html(title);
19958
19959                    var self = this;
19960                    var $toolbar = this.button.toolbar();
19961                    var $box = $toolbar.closest('.redactor-toolbar-box');
19962                    $box = ($box.length === 0) ? $toolbar : $box;
19963                    $box.prepend($tooltip);
19964                    $btn.on('mouseover', function()
19965                    {
19966                        if ($(this).hasClass('redactor-button-disabled'))
19967                        {
19968                            return;
19969                        }
19970
19971                        var pos = ($toolbar.hasClass('toolbar-fixed-box')) ? $btn.offset() : $btn.position();
19972                        pos = (self.opts.toolbarFixedTarget !== document) ? $btn.position() : pos;
19973                        var top = ($toolbar.hasClass('toolbar-fixed-box')) ? $btn.position().top : pos.top;
19974                        var height = $btn.innerHeight();
19975                        var width = $btn.innerWidth();
19976                        var posBox = ($toolbar.hasClass('toolbar-fixed-box')) ? 'fixed' : 'absolute';
19977                        posBox = (self.opts.toolbarFixedTarget !== document) ? 'absolute' : posBox;
19978                        var scrollFix = (self.opts.toolbarFixedTarget !== document) ? $toolbar.position().top : 0;
19979
19980                        $tooltip.show();
19981                        $tooltip.css({
19982                            top: (top + height + scrollFix) + 'px',
19983                            left: (pos.left + width/2 - $tooltip.innerWidth()/2) + 'px',
19984                            position: posBox
19985                        });
19986
19987                    }).on('mouseout', function()
19988                    {
19989                        $tooltip.hide();
19990                    });
19991				},
19992				build: function(btnName, btnObject)
19993				{
19994					if (this.opts.toolbar === false)
19995					{
19996						return;
19997					}
19998
19999                    var title = (typeof btnObject.label !== 'undefined') ? btnObject.label : btnObject.title;
20000					var $button = $('<a href="javascript:void(null);" alt="' + title + '" rel="' + btnName + '" />')
20001
20002                    $button.addClass('re-button re-' + btnName);
20003					$button.attr({ 'role': 'button', 'aria-label': title, 'tabindex': '-1' });
20004
20005                    if (typeof btnObject.icon !== 'undefined' && !this.opts.buttonsTextLabeled)
20006                    {
20007    					var $icon = $('<i>');
20008					    $icon.addClass('re-icon-' + btnName);
20009
20010					    $button.append($icon);
20011					    $button.addClass('re-button-icon');
20012
20013                        this.button.buildButtonTooltip($button, title);
20014					}
20015					else
20016					{
20017    					$button.html(btnObject.title);
20018					}
20019
20020					// click
20021					if (btnObject.func || btnObject.command || btnObject.dropdown)
20022					{
20023						this.button.setEvent($button, btnName, btnObject);
20024					}
20025
20026					// dropdown
20027					if (btnObject.dropdown)
20028					{
20029						$button.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
20030
20031						var $dropdown = $('<ul class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">');
20032						$button.data('dropdown', $dropdown);
20033						this.dropdown.build(btnName, $dropdown, btnObject.dropdown);
20034					}
20035
20036					this.button.buttons.push($button);
20037
20038					return $button;
20039				},
20040				getButtons: function()
20041				{
20042					return this.button.toolbar().find('a.re-button');
20043				},
20044				getButtonsKeys: function()
20045				{
20046					return this.button.buttons;
20047				},
20048				setEvent: function($button, btnName, btnObject)
20049				{
20050					$button.on('mousedown', $.proxy(function(e)
20051					{
20052						e.preventDefault();
20053
20054						if ($button.hasClass('redactor-button-disabled'))
20055						{
20056							return false;
20057						}
20058
20059						var type = 'func';
20060						var callback = btnObject.func;
20061
20062						if (btnObject.command)
20063						{
20064							type = 'command';
20065							callback = btnObject.command;
20066						}
20067						else if (btnObject.dropdown)
20068						{
20069							type = 'dropdown';
20070							callback = false;
20071						}
20072
20073						this.button.toggle(e, btnName, type, callback);
20074
20075						return false;
20076
20077					}, this));
20078				},
20079				toggle: function(e, btnName, type, callback, args)
20080				{
20081
20082					if (this.detect.isIe() || !this.detect.isDesktop())
20083					{
20084						this.utils.freezeScroll();
20085						e.returnValue = false;
20086					}
20087
20088					if (type === 'command')
20089					{
20090						this.inline.format(callback);
20091					}
20092					else if (type === 'dropdown')
20093					{
20094						this.dropdown.show(e, btnName);
20095					}
20096					else
20097					{
20098						this.button.clickCallback(e, callback, btnName, args);
20099					}
20100
20101					if (type !== 'dropdown')
20102					{
20103						this.dropdown.hideAll(false);
20104					}
20105
20106					if (this.opts.air && type !== 'dropdown')
20107					{
20108						this.air.hide(e);
20109					}
20110
20111					if (this.detect.isIe() || !this.detect.isDesktop())
20112					{
20113						this.utils.unfreezeScroll();
20114					}
20115				},
20116				clickCallback: function(e, callback, btnName, args)
20117				{
20118					var func;
20119
20120					args = (typeof args === 'undefined') ? btnName : args;
20121
20122					if ($.isFunction(callback))
20123					{
20124						callback.call(this, btnName);
20125					}
20126					else if (callback.search(/\./) !== '-1')
20127					{
20128						func = callback.split('.');
20129						if (typeof this[func[0]] === 'undefined')
20130						{
20131							return;
20132						}
20133
20134						if (typeof args === 'object')
20135						{
20136							this[func[0]][func[1]].apply(this, args);
20137						}
20138						else
20139						{
20140							this[func[0]][func[1]].call(this, args);
20141						}
20142					}
20143					else
20144					{
20145
20146						if (typeof args === 'object')
20147						{
20148							this[callback].apply(this, args);
20149						}
20150						else
20151						{
20152							this[callback].call(this, args);
20153						}
20154					}
20155
20156					this.observe.buttons(e, btnName);
20157
20158				},
20159				all: function()
20160				{
20161					return this.button.buttons;
20162				},
20163				get: function(key)
20164				{
20165					if (this.opts.toolbar === false)
20166					{
20167						return;
20168					}
20169
20170					return this.button.toolbar().find('a.re-' + key);
20171				},
20172				set: function(key, title)
20173				{
20174					if (this.opts.toolbar === false)
20175					{
20176						return;
20177					}
20178
20179					var $btn = this.button.toolbar().find('a.re-' + key);
20180
20181					$btn.html(title).attr('aria-label', title);
20182
20183					return $btn;
20184				},
20185				add: function(key, title)
20186				{
20187					if (this.button.isAdded(key) !== true)
20188					{
20189						return $();
20190					}
20191
20192					var btn = this.button.build(key, { title: title });
20193
20194					this.button.toolbar().append($('<li>').append(btn));
20195
20196					return btn;
20197				},
20198				addFirst: function(key, title)
20199				{
20200					if (this.button.isAdded(key) !== true)
20201					{
20202						return $();
20203					}
20204
20205					var btn = this.button.build(key, { title: title });
20206
20207					this.button.toolbar().prepend($('<li>').append(btn));
20208
20209					return btn;
20210				},
20211				addAfter: function(afterkey, key, title)
20212				{
20213					if (this.button.isAdded(key) !== true)
20214					{
20215						return $();
20216					}
20217
20218					var btn = this.button.build(key, { title: title });
20219					var $btn = this.button.get(afterkey);
20220
20221					if ($btn.length !== 0)
20222					{
20223						$btn.parent().after($('<li>').append(btn));
20224					}
20225					else
20226					{
20227						this.button.toolbar().append($('<li>').append(btn));
20228					}
20229
20230					return btn;
20231				},
20232				addBefore: function(beforekey, key, title)
20233				{
20234					if (this.button.isAdded(key) !== true)
20235					{
20236						return $();
20237					}
20238
20239					var btn = this.button.build(key, { title: title });
20240					var $btn = this.button.get(beforekey);
20241
20242					if ($btn.length !== 0)
20243					{
20244						$btn.parent().before($('<li>').append(btn));
20245					}
20246					else
20247					{
20248						this.button.toolbar().append($('<li>').append(btn));
20249					}
20250
20251					return btn;
20252				},
20253				isAdded: function(key)
20254				{
20255	                var index = this.opts.buttonsHideOnMobile.indexOf(key);
20256                    if (this.opts.toolbar === false || (index !== -1 && this.detect.isMobile()))
20257                    {
20258					    return false;
20259					}
20260
20261					return true;
20262				},
20263				setIcon: function($btn, icon)
20264				{
20265    				if (!this.opts.buttonsTextLabeled)
20266    				{
20267    					$btn.html(icon).addClass('re-button-icon');
20268					    this.button.buildButtonTooltip($btn, $btn.attr('alt'));
20269					}
20270				},
20271				changeIcon: function(key, newIconClass)
20272				{
20273                    var $btn = this.button.get(key);
20274                    if ($btn.length !== 0)
20275					{
20276    					$btn.find('i').removeAttr('class').addClass('re-icon-' + newIconClass);
20277    				}
20278				},
20279				addCallback: function($btn, callback)
20280				{
20281					if (typeof $btn === 'undefined' || this.opts.toolbar === false)
20282					{
20283						return;
20284					}
20285
20286					var type = (callback === 'dropdown') ? 'dropdown' : 'func';
20287					var key = $btn.attr('rel');
20288					$btn.on('mousedown', $.proxy(function(e)
20289					{
20290						if ($btn.hasClass('redactor-button-disabled'))
20291						{
20292							return false;
20293						}
20294
20295						this.button.toggle(e, key, type, callback);
20296
20297					}, this));
20298				},
20299				addDropdown: function($btn, dropdown)
20300				{
20301					if (this.opts.toolbar === false)
20302					{
20303						return;
20304					}
20305
20306					$btn.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
20307
20308					var key = $btn.attr('rel');
20309					this.button.addCallback($btn, 'dropdown');
20310
20311					var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + key + '" style="display: none;">');
20312					$btn.data('dropdown', $dropdown);
20313
20314					// build dropdown
20315					if (dropdown)
20316					{
20317						this.dropdown.build(key, $dropdown, dropdown);
20318					}
20319
20320					return $dropdown;
20321				},
20322				setActive: function(key)
20323				{
20324					this.button.get(key).addClass('redactor-act');
20325				},
20326				setInactive: function(key)
20327				{
20328					this.button.get(key).removeClass('redactor-act');
20329				},
20330				setInactiveAll: function(key)
20331				{
20332					var $btns = this.button.toolbar().find('a.re-button');
20333
20334					if (typeof key !== 'undefined')
20335					{
20336						$btns = $btns.not('.re-' + key);
20337					}
20338
20339					$btns.removeClass('redactor-act');
20340				},
20341				disable: function(key)
20342				{
20343					this.button.get(key).addClass('redactor-button-disabled');
20344				},
20345				enable: function(key)
20346				{
20347					this.button.get(key).removeClass('redactor-button-disabled');
20348				},
20349				disableAll: function(key)
20350				{
20351					var $btns = this.button.toolbar().find('a.re-button');
20352					if (typeof key !== 'undefined')
20353					{
20354						$btns = $btns.not('.re-' + key);
20355					}
20356
20357					$btns.addClass('redactor-button-disabled');
20358				},
20359				enableAll: function()
20360				{
20361					this.button.toolbar().find('a.re-button').removeClass('redactor-button-disabled');
20362				},
20363				remove: function(key)
20364				{
20365					this.button.get(key).remove();
20366				}
20367			};
20368		},
20369
20370		// =caret
20371		caret: function()
20372		{
20373			return {
20374				set: function(node1, node2, end)
20375				{
20376                    var cs = this.core.editor().scrollTop();
20377                    this.core.editor().focus();
20378                    this.core.editor().scrollTop(cs);
20379
20380					end = (typeof end === 'undefined') ? 0 : 1;
20381
20382					node1 = node1[0] || node1;
20383					node2 = node2[0] || node2;
20384
20385					var sel = this.selection.get();
20386					var range = this.selection.range(sel);
20387
20388					try
20389					{
20390						range.setStart(node1, 0);
20391						range.setEnd(node2, end);
20392					}
20393					catch (e) {}
20394
20395					this.selection.update(sel, range);
20396				},
20397				prepare: function(node)
20398				{
20399					// firefox focus
20400					if (this.detect.isFirefox() && typeof this.start !== 'undefined')
20401					{
20402						this.core.editor().focus();
20403					}
20404
20405					return node[0] || node;
20406				},
20407				start: function(node)
20408				{
20409					var sel, range;
20410					node = this.caret.prepare(node);
20411
20412					if (!node)
20413					{
20414						return;
20415					}
20416
20417					if (node.tagName === 'BR')
20418					{
20419						return this.caret.before(node);
20420					}
20421
20422					var $first = $(node).children().first();
20423
20424					// empty or inline tag
20425					var inline = this.utils.isInlineTag(node.tagName);
20426					if (node.innerHTML === '' || inline)
20427					{
20428                        this.caret.setStartEmptyOrInline(node, inline);
20429					}
20430					// empty inline inside
20431                    else if ($first && $first.length !== 0 && this.utils.isInlineTag($first[0].tagName) && $first.text() === '')
20432                    {
20433                       this.caret.setStartEmptyOrInline($first[0], true);
20434                    }
20435                    // block tag
20436					else
20437					{
20438                        sel = window.getSelection();
20439                        sel.removeAllRanges();
20440
20441                        range = document.createRange();
20442                        range.selectNodeContents(node);
20443                        range.collapse(true);
20444                        sel.addRange(range);
20445					}
20446
20447
20448				},
20449				setStartEmptyOrInline: function(node, inline)
20450				{
20451                    var sel = window.getSelection();
20452				    var range = document.createRange();
20453					var textNode = document.createTextNode('\u200B');
20454
20455					range.setStart(node, 0);
20456					range.insertNode(textNode);
20457					range.setStartAfter(textNode);
20458					range.collapse(true);
20459
20460					sel.removeAllRanges();
20461					sel.addRange(range);
20462
20463					// remove invisible text node
20464					if (!inline)
20465					{
20466						this.core.editor().on('keydown.redactor-remove-textnode', function()
20467						{
20468							$(textNode).remove();
20469							$(this).off('keydown.redactor-remove-textnode');
20470						});
20471					}
20472				},
20473				end: function(node)
20474				{
20475					var sel, range;
20476					node = this.caret.prepare(node);
20477
20478					if (!node)
20479					{
20480						return;
20481					}
20482
20483					// empty node
20484					if (node.tagName !== 'BR' && node.innerHTML === '')
20485					{
20486						return this.caret.start(node);
20487					}
20488
20489					// br
20490					if (node.tagName === 'BR')
20491					{
20492						var space = document.createElement('span');
20493						space.className = 'redactor-invisible-space';
20494						space.innerHTML = '&#x200b;';
20495
20496						$(node).after(space);
20497
20498						sel = window.getSelection();
20499						sel.removeAllRanges();
20500
20501						range = document.createRange();
20502
20503						range.setStartBefore(space);
20504						range.setEndBefore(space);
20505						sel.addRange(range);
20506
20507						$(space).replaceWith(function()
20508						{
20509							return $(this).contents();
20510						});
20511
20512						return;
20513					}
20514
20515					if (node.lastChild && node.lastChild.nodeType === 1)
20516					{
20517						return this.caret.after(node.lastChild);
20518					}
20519
20520                    var sel = window.getSelection();
20521                    if (sel.getRangeAt || sel.rangeCount)
20522                    {
20523                        try {
20524                            var range = sel.getRangeAt(0);
20525                            range.selectNodeContents(node);
20526                            range.collapse(false);
20527
20528                            sel.removeAllRanges();
20529                            sel.addRange(range);
20530                        }
20531                        catch(e) {}
20532                    }
20533				},
20534				after: function(node)
20535				{
20536					var sel, range;
20537					node = this.caret.prepare(node);
20538
20539
20540					if (!node)
20541					{
20542						return;
20543					}
20544
20545					if (node.tagName === 'BR')
20546					{
20547						return this.caret.end(node);
20548					}
20549
20550					// block tag
20551					if (this.utils.isBlockTag(node.tagName))
20552					{
20553						var next = this.caret.next(node);
20554
20555						if (typeof next === 'undefined')
20556						{
20557							this.caret.end(node);
20558						}
20559						else
20560						{
20561							// table
20562							if (next.tagName === 'TABLE')
20563							{
20564								next = $(next).find('th, td').first()[0];
20565							}
20566							// list
20567							else if (next.tagName === 'UL' || next.tagName === 'OL')
20568							{
20569								next = $(next).find('li').first()[0];
20570							}
20571
20572							this.caret.start(next);
20573						}
20574
20575						return;
20576					}
20577
20578					// inline tag
20579					var textNode = document.createTextNode('\u200B');
20580
20581					sel = window.getSelection();
20582					sel.removeAllRanges();
20583
20584					range = document.createRange();
20585					range.setStartAfter(node);
20586					range.insertNode(textNode);
20587					range.setStartAfter(textNode);
20588					range.collapse(true);
20589
20590					sel.addRange(range);
20591
20592				},
20593				before: function(node)
20594				{
20595					var sel, range;
20596					node = this.caret.prepare(node);
20597
20598
20599					if (!node)
20600					{
20601						return;
20602					}
20603
20604					// block tag
20605					if (this.utils.isBlockTag(node.tagName))
20606					{
20607						var prev = this.caret.prev(node);
20608
20609						if (typeof prev === 'undefined')
20610						{
20611							this.caret.start(node);
20612						}
20613						else
20614						{
20615							// table
20616							if (prev.tagName === 'TABLE')
20617							{
20618								prev = $(prev).find('th, td').last()[0];
20619							}
20620							// list
20621							else if (prev.tagName === 'UL' || prev.tagName === 'OL')
20622							{
20623								prev = $(prev).find('li').last()[0];
20624							}
20625
20626							this.caret.end(prev);
20627						}
20628
20629						return;
20630					}
20631
20632					// inline tag
20633					sel = window.getSelection();
20634					sel.removeAllRanges();
20635
20636					range = document.createRange();
20637
20638			        range.setStartBefore(node);
20639			        range.collapse(true);
20640
20641			        sel.addRange(range);
20642				},
20643				next: function(node)
20644				{
20645					var $next = $(node).next();
20646					if ($next.hasClass('redactor-script-tag, redactor-selection-marker'))
20647					{
20648						return $next.next()[0];
20649					}
20650					else
20651					{
20652						return $next[0];
20653					}
20654				},
20655				prev: function(node)
20656				{
20657					var $prev = $(node).prev();
20658					if ($prev.hasClass('redactor-script-tag, redactor-selection-marker'))
20659					{
20660						return $prev.prev()[0];
20661					}
20662					else
20663					{
20664						return $prev[0];
20665					}
20666				},
20667
20668				// #backward
20669				offset: function(node)
20670				{
20671					return this.offset.get(node);
20672				}
20673
20674			};
20675		},
20676
20677		// =clean
20678		clean: function()
20679		{
20680			return {
20681				onSet: function(html)
20682				{
20683					html = this.clean.savePreCode(html);
20684					html = this.clean.saveFormTags(html);
20685
20686					// convert script tag
20687					if (this.opts.script)
20688					{
20689						html = html.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi, '<pre class="redactor-script-tag" $1>$2</pre>');
20690					}
20691
20692					// converting entity
20693					html = html.replace(/\$/g, '&#36;');
20694					html = html.replace(/&amp;/g, '&');
20695
20696					// replace special characters in links
20697					html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1&reg$2">');
20698
20699					// save markers
20700					html = html.replace(/<span id="selection-marker-1"(.*?[^>]?)>​<\/span>/gi, '###marker1###');
20701					html = html.replace(/<span id="selection-marker-2"(.*?[^>]?)>​<\/span>/gi, '###marker2###');
20702
20703					// replace tags
20704					var self = this;
20705					var $div = $("<div/>").html($.parseHTML(html, document, true));
20706
20707					var replacement = this.opts.replaceTags;
20708					if (replacement)
20709					{
20710                        var keys = Object.keys(this.opts.replaceTags);
20711    					$div.find(keys.join(',')).each(function(i,s)
20712    					{
20713    						self.utils.replaceToTag(s, replacement[s.tagName.toLowerCase()]);
20714    					});
20715					}
20716
20717					// add span marker
20718					$div.find('span, a').attr('data-redactor-span', true);
20719
20720					// add style cache
20721					$div.find(this.opts.inlineTags.join(',')).each(function()
20722					{
20723    					// add style cache
20724    					var $el = $(this);
20725
20726    					if ($el.attr('style'))
20727    					{
20728        					$el.attr('data-redactor-style-cache', $el.attr('style'));
20729    					}
20730					});
20731
20732					html = $div.html();
20733
20734					// remove tags
20735					var tags = ['font', 'html', 'head', 'link', 'body', 'meta', 'applet'];
20736					if (!this.opts.script)
20737					{
20738						tags.push('script');
20739					}
20740
20741					html = this.clean.stripTags(html, tags);
20742
20743					// remove html comments
20744					if (this.opts.removeComments)
20745					{
20746					    html = html.replace(/<!--[\s\S]*?-->/gi, '');
20747					}
20748
20749					// paragraphize
20750					html = this.paragraphize.load(html);
20751
20752					// restore markers
20753					html = html.replace('###marker1###', '<span id="selection-marker-1" class="redactor-selection-marker">​</span>');
20754					html = html.replace('###marker2###', '<span id="selection-marker-2" class="redactor-selection-marker">​</span>');
20755
20756					// empty
20757					if (html.search(/^(||\s||<br\s?\/?>||&nbsp;)$/i) !== -1)
20758					{
20759						return this.opts.emptyHtml;
20760					}
20761
20762					return html;
20763				},
20764				onGet: function(html)
20765				{
20766					return this.clean.onSync(html);
20767				},
20768				onSync: function(html)
20769				{
20770					// remove invisible spaces
20771					html = html.replace(/\u200B/g, '');
20772					html = html.replace(/&#x200b;/gi, '');
20773					//html = html.replace(/&nbsp;&nbsp;/gi, '&nbsp;');
20774
20775					if (html.search(/^<p>(||\s||<br\s?\/?>||&nbsp;)<\/p>$/i) !== -1)
20776					{
20777						return '';
20778					}
20779
20780					// remove image resize
20781					html = html.replace(/<span(.*?)id="redactor-image-box"(.*?[^>])>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>');
20782					html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?[^>])>(.*?)<\/span>/gi, '');
20783					html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, '');
20784					html = html.replace(/<img(.*?)style="(.*?)opacity: 0\.5;(.*?)"(.*?)>/gi, '<img$1style="$2$3"$4>');
20785
20786					var $div = $("<div/>").html($.parseHTML(html, document, true));
20787
20788					// remove empty atributes
20789					$div.find('*[style=""]').removeAttr('style');
20790					$div.find('*[class=""]').removeAttr('class');
20791					$div.find('*[rel=""]').removeAttr('rel');
20792					$div.find('*[data-image=""]').removeAttr('data-image');
20793					$div.find('*[alt=""]').removeAttr('alt');
20794					$div.find('*[title=""]').removeAttr('title');
20795					$div.find('*[data-redactor-style-cache]').removeAttr('data-redactor-style-cache');
20796
20797					// remove markers
20798					$div.find('.redactor-invisible-space, .redactor-unlink').each(function()
20799					{
20800						$(this).contents().unwrap();
20801					});
20802
20803					// remove span without attributes & span marker
20804				    $div.find('span, a').removeAttr('data-redactor-span data-redactor-style-cache').each(function()
20805					{
20806    					if (this.attributes.length === 0)
20807    					{
20808						    $(this).contents().unwrap();
20809						}
20810					});
20811
20812					// remove rel attribute from img
20813					$div.find('img').removeAttr('rel');
20814
20815					$div.find('.redactor-selection-marker, #redactor-insert-marker').remove();
20816
20817					html = $div.html();
20818
20819					// reconvert script tag
20820					if (this.opts.script)
20821					{
20822						html = html.replace(/<pre class="redactor-script-tag"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi, '<script$1>$2</script>');
20823					}
20824
20825					// restore form tag
20826					html = this.clean.restoreFormTags(html);
20827
20828					// remove br in|of li/header tags
20829					html = html.replace(new RegExp('<br\\s?/?></h', 'gi'), '</h');
20830					html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>');
20831					html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>');
20832
20833					// pre class
20834					html = html.replace(/<pre>/gi, "<pre>\n");
20835					if (this.opts.preClass)
20836					{
20837						html = html.replace(/<pre>/gi, '<pre class="' + this.opts.preClass + '">');
20838					}
20839
20840					// link nofollow
20841					if (this.opts.linkNofollow)
20842					{
20843						html = html.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi, '<a$1$2>');
20844						html = html.replace(/<a(.*?[^>])>/gi, '<a$1 rel="nofollow">');
20845					}
20846
20847					// replace special characters
20848					var chars = {
20849						'\u2122': '&trade;',
20850						'\u00a9': '&copy;',
20851						'\u2026': '&hellip;',
20852						'\u2014': '&mdash;',
20853						'\u2010': '&dash;'
20854					};
20855
20856					$.each(chars, function(i,s)
20857					{
20858						html = html.replace(new RegExp(i, 'g'), s);
20859					});
20860
20861					html = html.replace(/&amp;/g, '&');
20862
20863					// remove empty paragpraphs
20864					//html = html.replace(/<p><\/p>/gi, "");
20865
20866					// remove new lines
20867                    html = html.replace(/\n{2,}/g, "\n");
20868
20869                    // remove all newlines
20870                    if (this.opts.removeNewlines)
20871                    {
20872                        html = html.replace(/\r?\n/g, "");
20873                    }
20874
20875					return html;
20876				},
20877				onPaste: function(html, data, insert)
20878				{
20879					// if paste event
20880					if (insert !== true)
20881					{
20882    					// remove google docs markers
20883                        html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
20884    					html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
20885
20886                        // google docs styles
20887                        html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi, '<b><i>$2</i></b>');
20888                        html = html.replace(/<span[^>]*(font-style: italic; font-weight: 700|font-weight: 700; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi, '<b><i>$2</i></b>');
20889                        html = html.replace(/<span[^>]*font-style: italic[^>]*>([\w\W]*?)<\/span>/gi, '<i>$1</i>');
20890                        html = html.replace(/<span[^>
20890]*font-weight: bold[^>]*>([\w\W]*?)<\/span>/gi, '<b>$1</b>');
20891                        html = html.replace(/<span[^>]*font-weight: 700[^>]*>([\w\W]*?)<\/span>/gi, '<b>$1</b>');
20892
20893                        // op tag
20894                        html = html.replace(/<o:p[^>]*>/gi, '');
20895                        html = html.replace(/<\/o:p>/gi, '');
20896
20897						var msword = this.clean.isHtmlMsWord(html);
20898						if (msword)
20899						{
20900							html = this.clean.cleanMsWord(html);
20901						}
20902					}
20903
20904					html = $.trim(html);
20905
20906					if (data.pre)
20907					{
20908						if (this.opts.preSpaces)
20909						{
20910							html = html.replace(/\t/g, new Array(this.opts.preSpaces + 1).join(' '));
20911						}
20912					}
20913					else
20914					{
20915
20916						html = this.clean.replaceBrToNl(html);
20917						html = this.clean.removeTagsInsidePre(html);
20918					}
20919
20920					// if paste event
20921					if (insert !== true)
20922					{
20923						html = this.clean.removeEmptyInlineTags(html);
20924
20925						if (data.encode === false)
20926						{
20927							html = html.replace(/&/g, '&amp;');
20928							html = this.clean.convertTags(html, data);
20929							html = this.clean.getPlainText(html);
20930							html = this.clean.reconvertTags(html, data);
20931						}
20932
20933					}
20934
20935					if (data.text)
20936					{
20937						html = this.clean.replaceNbspToSpaces(html);
20938						html = this.clean.getPlainText(html);
20939					}
20940
20941					if (data.lists)
20942					{
20943    					html = html.replace("\n", '<br>');
20944    				}
20945
20946					if (data.encode)
20947					{
20948						html = this.clean.encodeHtml(html);
20949					}
20950
20951					if (data.paragraphize)
20952					{
20953    					// ff bugfix
20954                        html = html.replace(/ \n/g, ' ');
20955                        html = html.replace(/\n /g, ' ');
20956
20957						html = this.paragraphize.load(html);
20958
20959						// remove empty p
20960						html = html.replace(/<p><\/p>/g, '');
20961					}
20962
20963					// remove paragraphs form lists (google docs bug)
20964					html = html.replace(/<li><p>/g, '<li>');
20965					html = html.replace(/<\/p><\/li>/g, '</li>');
20966
20967					return html;
20968
20969				},
20970				getCurrentType: function(html, insert)
20971				{
20972					var blocks = this.selection.blocks();
20973
20974					var data = {
20975						text: false,
20976						encode: false,
20977						paragraphize: true,
20978						line: this.clean.isHtmlLine(html),
20979						blocks: this.clean.isHtmlBlocked(html),
20980						pre: false,
20981						lists: false,
20982						block: true,
20983						inline: true,
20984						links: true,
20985						images: true
20986					};
20987
20988					if (blocks.length === 1 && this.utils.isCurrentOrParent(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'figcaption']))
20989					{
20990						data.text = true;
20991						data.paragraphize = false;
20992						data.inline = false;
20993						data.images = false;
20994						data.links = false;
20995						data.line = true;
20996					}
20997					else if (this.opts.type === 'inline' || this.opts.enterKey === false)
20998					{
20999						data.paragraphize = false;
21000						data.block = false;
21001						data.line = true;
21002					}
21003					else if (blocks.length === 1 && this.utils.isCurrentOrParent(['li']))
21004					{
21005						data.lists = true;
21006						data.block = false;
21007						data.paragraphize = false;
21008						data.images = false;
21009					}
21010					else if (blocks.length === 1 && this.utils.isCurrentOrParent(['th', 'td', 'blockquote']))
21011					{
21012						data.block = false;
21013						data.paragraphize = false;
21014
21015					}
21016					else if (this.opts.type === 'pre' || (blocks.length === 1 && this.utils.isCurrentOrParent('pre')))
21017					{
21018						data.inline = false;
21019						data.block = false;
21020						data.encode = true;
21021						data.pre = true;
21022						data.paragraphize = false;
21023						data.images = false;
21024						data.links = false;
21025					}
21026
21027					if (data.line === true)
21028					{
21029						data.paragraphize = false;
21030					}
21031
21032					if (insert === true)
21033					{
21034						data.text = false;
21035					}
21036
21037					return data;
21038
21039				},
21040				isHtmlBlocked: function(html)
21041				{
21042					var match1 = html.match(new RegExp('</(' + this.opts.blockTags.join('|' ).toUpperCase() + ')>', 'gi'));
21043					var match2 = html.match(new RegExp('<hr(.*?[^>])>', 'gi'));
21044
21045					return (match1 === null && match2 === null) ? false : true;
21046				},
21047				isHtmlLine: function(html)
21048				{
21049					if (this.clean.isHtmlBlocked(html))
21050					{
21051						return false;
21052					}
21053
21054					var matchBR = html.match(/<br\s?\/?>/gi);
21055					var matchNL = html.match(/\n/gi);
21056
21057					return (!matchBR && !matchNL) ? true : false;
21058				},
21059				isHtmlMsWord: function(html)
21060				{
21061					return html.match(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i);
21062				},
21063				removeEmptyInlineTags: function(html)
21064				{
21065					var tags = this.opts.inlineTags;
21066    				var $div = $("<div/>").html($.parseHTML(html, document, true));
21067                    var self = this;
21068
21069					var $spans = $div.find('span');
21070					var $tags = $div.find(tags.join(','));
21071
21072					$tags.removeAttr('style');
21073
21074					$tags.each(function()
21075					{
21076    					var tagHtml = $(this).html();
21077    					if (this.attributes.length === 0 && self.utils.isEmpty(tagHtml))
21078    					{
21079        					$(this).replaceWith(function()
21080        					{
21081        						return $(this).contents();
21082        					});
21083    					}
21084					});
21085
21086                    $spans.each(function()
21087					{
21088    					var tagHtml = $(this).html();
21089    					if (this.attributes.length === 0)
21090    					{
21091        					$(this).replaceWith(function()
21092        					{
21093        						return $(this).contents();
21094        					});
21095    					}
21096					});
21097
21098					html = $div.html();
21099
21100					// convert php tags
21101					html = html.replace('<!--?php', '<?php');
21102					html = html.replace('<!--?', '<?');
21103					html = html.replace('?-->', '?>');
21104
21105					$div.remove();
21106
21107					return html;
21108				},
21109				cleanMsWord: function(html)
21110				{
21111    				html = html.replace(/<!--[\s\S]*?-->/g, "");
21112    				html = html.replace(/<o:p>[\s\S]*?<\/o:p>/gi, '');
21113					html = html.replace(/\n/g, " ");
21114					html = html.replace(/<br\s?\/?>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n\n');
21115
21116					// lists
21117					var $div = $("<div/>").html(html);
21118
21119					var lastList = false;
21120					var lastLevel = 1;
21121					var listsIds = [];
21122
21123					$div.find("p[style]").each(function()
21124					{
21125						var matches = $(this).attr('style').match(/mso\-list\:l([0-9]+)\slevel([0-9]+)/);
21126
21127						if (matches)
21128						{
21129							var currentList = parseInt(matches[1]);
21130							var currentLevel = parseInt(matches[2]);
21131							var listType = $(this).html().match(/^[\w]+\./) ? "ol" : "ul";
21132
21133							var $li = $("<li/>").html($(this).html());
21134
21135							$li.html($li.html().replace(/^([\w\.]+)</, '<'));
21136							$li.find("span:first").remove();
21137
21138							if (currentLevel == 1 && $.inArray(currentList, listsIds) == -1)
21139							{
21140								var $list = $("<" + listType + "/>").attr({"data-level": currentLevel, "data-list": currentList}).html($li);
21141								$(this).replaceWith($list);
21142
21143								lastList = currentList;
21144								listsIds.push(currentList);
21145							}
21146							else
21147							{
21148								if (currentLevel > lastLevel)
21149								{
21150									var $prevList = $div.find('[data-level="' + lastLevel + '"][data-list="' + lastList + '"]');
21151									var $lastList = $prevList;
21152
21153									for(var i = lastLevel; i < currentLevel; i++)
21154									{
21155										$list = $("<" + listType + "/>");
21156										$list.appendTo($lastList.find("li").last());
21157
21158										$lastList = $list;
21159									}
21160
21161									$lastList.attr({"data-level": currentLevel, "data-list": currentList}).html($li);
21162
21163								}
21164								else
21165								{
21166									var $prevList = $div.find('[data-level="' + currentLevel + '"][data-list="' + currentList + '"]').last();
21167
21168									$prevList.append($li);
21169								}
21170
21171								lastLevel = currentLevel;
21172								lastList = currentList;
21173
21174								$(this).remove();
21175							}
21176						}
21177					});
21178
21179					$div.find('[data-level][data-list]').removeAttr('data-level data-list');
21180					html = $div.html();
21181
21182					return html;
21183				},
21184				replaceNbspToSpaces: function(html)
21185				{
21186					return html.replace('&nbsp;', ' ');
21187				},
21188				replaceBrToNl: function(html)
21189				{
21190					return html.replace(/<br\s?\/?>/gi, '\n');
21191				},
21192				replaceNlToBr: function(html)
21193				{
21194					return html.replace(/\n/g, '<br />');
21195				},
21196				convertTags: function(html, data)
21197				{
21198                    var $div = $('<div>').html(html);
21199
21200                    // remove iframe
21201					$div.find('iframe').remove();
21202
21203					// link target & attrs
21204					var $links = $div.find('a');
21205					$links.removeAttr('style');
21206					if (this.opts.pasteLinkTarget !== false)
21207					{
21208    					$links.attr('target', this.opts.pasteLinkTarget);
21209					}
21210
21211                    // links
21212                    if (data.links && this.opts.pasteLinks)
21213                    {
21214                    	$div.find('a').each(function(i, link)
21215                    	{
21216                    		if (link.href)
21217                    		{
21218                    			var tmp = '#####[a href="' + link.href + '"';
21219                    			var attr;
21220                    			for (var j = 0, length = link.attributes.length; j < length; j++)
21221                    			{
21222                    				attr = link.attributes.item(j);
21223                    				if (attr.name !== 'href')
21224                    				{
21225                    					tmp += ' ' + attr.name + '="' + attr.value + '"';
21226                    				}
21227                    			}
21228
21229                    			link.outerHTML = tmp + ']#####' + link.innerHTML + '#####[/a]#####';
21230                    		}
21231                    	});
21232                    }
21233
21234                    html = $div.html();
21235
21236                    // images
21237					if (data.images && this.opts.pasteImages)
21238					{
21239						html = html.replace(/<img(.*?)src="(.*?)"(.*?[^>])>/gi, '#####[img$1src="$2"$3]#####');
21240					}
21241
21242					// plain text
21243					if (this.opts.pastePlainText)
21244					{
21245						return html;
21246					}
21247
21248					// all tags
21249					var blockTags = (data.lists) ? ['ul', 'ol', 'li'] : this.opts.pasteBlockTags;
21250
21251					var tags;
21252					if (data.block || data.lists)
21253					{
21254						tags = (data.inline) ? blockTags.concat(this.opts.pasteInlineTags) : blockTags;
21255					}
21256					else
21257					{
21258						tags = (data.inline) ? this.opts.pasteInlineTags : [];
21259					}
21260
21261					var len = tags.length;
21262					for (var i = 0; i < len; i++)
21263					{
21264						html = html.replace(new RegExp('<\/' + tags[i] + '>', 'gi'), '###/' + tags[i] + '###');
21265
21266						if (tags[i] === 'td' || tags[i] === 'th')
21267						{
21268							html = html.replace(new RegExp('<' + tags[i] + '(.*?[^>])((colspan|rowspan)="(.*?[^>])")?(.*?[^>])>
21268', 'gi'), '###' + tags[i] + ' $2###');
21269						}
21270						else if (this.utils.isInlineTag(tags[i]))
21271						{
21272    						html = html.replace(new RegExp('<' + tags[i] + '([^>]*)class="([^>]*)"[^>]*>', 'gi'), '###' + tags[i] + ' class="$2"###');
21273                            html = html.replace(new RegExp('<' + tags[i] + '([^>]*)data-redactor-style-cache="([^>]*)"[^>]*>', 'gi'), '###' + tags[i] + ' cache="$2"###');
21274                            html = html.replace(new RegExp('<' + tags[i] + '[^>]*>', 'gi'), '###' + tags[i] + '###');
21275						}
21276						else
21277						{
21278							html = html.replace(new RegExp('<' + tags[i] + '[^>]*>', 'gi'), '###' + tags[i] + '###');
21279						}
21280					}
21281
21282					return html;
21283
21284				},
21285				reconvertTags: function(html, data)
21286				{
21287					// links & images
21288					if ((data.links && this.opts.pasteLinks) || (data.images && this.opts.pasteImages))
21289					{
21290						html = html.replace(new RegExp('#####\\[', 'gi'), '<');
21291						html = html.replace(new RegExp('\\]#####', 'gi'), '>');
21292                    }
21293
21294					// plain text
21295					if (this.opts.pastePlainText)
21296					{
21297						return html;
21298					}
21299
21300					var blockTags = (data.lists) ? ['ul', 'ol', 'li'] : this.opts.pasteBlockTags;
21301
21302					var tags;
21303					if (data.block || data.lists)
21304					{
21305						tags = (data.inline) ? blockTags.concat(this.opts.pasteInlineTags) : blockTags;
21306					}
21307					else
21308					{
21309						tags = (data.inline) ? this.opts.pasteInlineTags : [];
21310					}
21311
21312					var len = tags.length;
21313					for (var i = 0; i < len; i++)
21314					{
21315						html = html.replace(new RegExp('###\/' + tags[i] + '###', 'gi'), '</' + tags[i] + '>');
21316                    }
21317
21318                    for (var i = 0; i < len; i++)
21319					{
21320    					html = html.replace(new RegExp('###' + tags[i] + '###', 'gi'), '<' + tags[i] + '>');
21321    				}
21322
21323					for (var i = 0; i < len; i++)
21324					{
21325                        if (tags[i] === 'td' || tags[i] === 'th')
21326                        {
21327						    html = html.replace(new RegExp('###' + tags[i] + '\s?(.*?[^#])###', 'gi'), '<' + tags[i] + '$1>');
21328                        }
21329                        else if (this.utils.isInlineTag(tags[i]))
21330                        {
21331
21332                            var spanMarker = (tags[i] === 'span') ? ' data-redactor-span="true"' : '';
21333
21334                            html = html.replace(new RegExp('###' + tags[i] + ' cache="(.*?[^#])"###', 'gi'), '<' + tags[i] + ' style="$1"' + spanMarker + ' data-redactor-style-cache="$1">');
21335                            html = html.replace(new RegExp('###' + tags[i] + '\s?(.*?[^#])###', 'gi'), '<' + tags[i] + '$1>');
21336                        }
21337                    }
21338
21339					return html;
21340
21341				},
21342				cleanPre: function(block)
21343				{
21344					block = (typeof block === 'undefined') ? $(this.selection.block()).closest('pre', this.core.editor()[0]) : block;
21345
21346					$(block).find('br').replaceWith(function()
21347					{
21348						return document.createTextNode('\n');
21349					});
21350
21351					$(block).find('p').replaceWith(function()
21352					{
21353						return $(this).contents();
21354					});
21355
21356				},
21357				removeTagsInsidePre: function(html)
21358				{
21359					var $div = $('<div />').append(html);
21360					$div.find('pre').replaceWith(function()
21361					{
21362						var str = $(this).html();
21363						str = str.replace(/<br\s?\/?>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n');
21364						str = str.replace(/(<([^>]+)>)/gi, '');
21365
21366						return $('<pre />').append(str);
21367					});
21368
21369					html = $div.html();
21370					$div.remove();
21371
21372					return html;
21373
21374				},
21375				getPlainText: function(html)
21376				{
21377					html = html.replace(/<!--[\s\S]*?-->/gi, '');
21378					html = html.replace(/<style[\s\S]*?style>/gi, '');
21379                    html = html.replace(/<p><\/p>/g, '');
21380					html = html.replace(/<\/div>|<\/li>|<\/td>/gi, '\n');
21381					html = html.replace(/<\/p>/gi, '\n\n');
21382					html = html.replace(/<\/H[1-6]>/gi, '\n\n');
21383
21384					var tmp = document.createElement('div');
21385					tmp.innerHTML = html;
21386					html = tmp.textContent || tmp.innerText;
21387
21388					return $.trim(html);
21389				},
21390				savePreCode: function(html)
21391				{
21392					html = this.clean.savePreFormatting(html);
21393					html = this.clean.saveCodeFormatting(html);
21394					html = this.clean.restoreSelectionMarkers(html);
21395
21396					return html;
21397				},
21398				savePreFormatting: function(html)
21399				{
21400					var pre = html.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi);
21401					if (pre === null)
21402					{
21403						return html;
21404					}
21405
21406					$.each(pre, $.proxy(function(i,s)
21407					{
21408    					var arr = [];
21409    					var codeTag = false;
21410    					var contents, attr1, attr2;
21411
21412    					if (s.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>/i))
21413    					{
21414        					arr = s.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>([\w\W]*?)<\/code>(([\n\r\s]+)?)<\/pre>/i);
21415        					codeTag = true;
21416
21417                            contents = arr[5];
21418                            attr1 = arr[1];
21419                            attr2 = arr[4];
21420    					}
21421                        else
21422						{
21423    						arr = s.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i);
21424
21425                            contents = arr[2];
21426                            attr1 = arr[1];
21427                        }
21428
21429						contents = contents.replace(/<br\s?\/?>/g, '\n');
21430						contents = contents.replace(/&nbsp;/g, ' ');
21431
21432						if (this.opts.preSpaces)
21433						{
21434							contents = contents.replace(/\t/g, new Array(this.opts.preSpaces + 1).join(' '));
21435						}
21436
21437                        contents = this.clean.encodeEntities(contents);
21438
21439						// $ fix
21440						contents = contents.replace(/\$/g, '&#36;');
21441
21442                        if (codeTag)
21443                        {
21444                            html = html.replace(s, '<pre' + attr1 + '><code' + attr2 + '>' + contents + '</code></pre>');
21445                        }
21446                        else
21447                        {
21448                            html = html.replace(s, '<pre' + attr1 + '>' + contents + '</pre>');
21449                        }
21450
21451
21452					}, this));
21453
21454					return html;
21455				},
21456				saveCodeFormatting: function(html)
21457				{
21458					var code = html.match(/<code(.*?)>([\w\W]*?)<\/code>/gi);
21459					if (code === null)
21460					{
21461						return html;
21462					}
21463
21464					$.each(code, $.proxy(function(i,s)
21465					{
21466						var arr = s.match(/<code(.*?)>([\w\W]*?)<\/code>/i);
21467
21468						arr[2] = arr[2].replace(/&nbsp;/g, ' ');
21469						arr[2] = this.clean.encodeEntities(arr[2]);
21470						arr[2] = arr[2].replace(/\$/g, '&#36;');
21471
21472						html = html.replace(s, '<code' + arr[1] + '>' + arr[2] + '</code>');
21473
21474					}, this));
21475
21476					return html;
21477				},
21478				restoreSelectionMarkers: function(html)
21479				{
21480					html = html.replace(/&lt;span id=&quot;selection-marker-([0-9])&quot; class=&quot;redactor-selection-marker&quot;&gt;​&lt;\/span&gt;/g, '<span id="selection-marker-$1" class="redactor-selection-marker">​</span>');
21481
21482					return html;
21483				},
21484				saveFormTags: function(html)
21485				{
21486					return html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
21487				},
21488				restoreFormTags: function(html)
21489				{
21490					return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
21491				},
21492				encodeHtml: function(html)
21493				{
21494					html = html.replace(/”/g, '"');
21495					html = html.replace(/“/g, '"');
21496					html = html.replace(/‘/g, '\'');
21497					html = html.replace(/’/g, '\'');
21498					html = this.clean.encodeEntities(html);
21499
21500					return html;
21501				},
21502				encodeEntities: function(str)
21503				{
21504					str = String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
21505					str = str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
21506
21507					return str;
21508				},
21509				stripTags: function(input, denied)
21510				{
21511					if (typeof denied === 'undefined')
21512					{
21513						return input.replace(/(<([^>]+)>)/gi, '');
21514					}
21515
21516				    var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
21517
21518				    return input.replace(tags, function ($0, $1)
21519				    {
21520				        return denied.indexOf($1.toLowerCase()) === -1 ? $0 : '';
21521				    });
21522				},
21523				removeMarkers: function(html)
21524				{
21525					return html.replace(/<span(.*?[^>]?)class="redactor-selection-marker"(.*?[^>]?)>([\w\W]*?)<\/span>/gi, '');
21526				},
21527				removeSpaces: function(html)
21528				{
21529					html = $.trim(html);
21530					html = html.replace(/\n/g, '');
21531					html = html.replace(/[\t]*/g, '');
21532					html = html.replace(/\n\s*\n/g, "\n");
21533					html = html.replace(/^[\s\n]*/g, ' ');
21534					html = html.replace(/[\s\n]*$/g, ' ');
21535					html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
21536					html = html.replace(/\n\n/g, "\n");
21537					html = html.replace(/\u200B/g, '');
21538
21539					return html;
21540				},
21541				removeSpacesHard: function(html)
21542				{
21543					html = $.trim(html);
21544					html = html.replace(/\n/g, '');
21545					html = html.replace(/[\t]*/g, '');
21546					html = html.replace(/\n\s*\n/g, "\n");
21547					html = html.replace(/^[\s\n]*/g, '');
21548					html = html.replace(/[\s\n]*$/g, '');
21549					html = html.replace( />\s{2,}</g, '><');
21550					html = html.replace(/\n\n/g, "\n");
21551					html = html.replace(/\u200B/g, '');
21552
21553					return html;
21554				},
21555				normalizeCurrentHeading: function()
21556				{
21557					var heading = this.selection.block();
21558					if (this.utils.isCurrentOrParentHeader() && heading)
21559					{
21560						heading.normalize();
21561					}
21562				}
21563			};
21564		},
21565
21566		// =code
21567		code: function()
21568		{
21569			return {
21570				syncFire: true,
21571				html: false,
21572				start: function(html)
21573				{
21574					html = $.trim(html);
21575					html = html.replace(/^(<span id="selection-marker-1" class="redactor-selection-marker">​<\/span>)/, '');
21576
21577					// clean
21578					if (this.opts.type === 'textarea')
21579					{
21580						html = this.clean.onSet(html);
21581					}
21582					else if (this.opts.type === 'div' && html === '')
21583					{
21584						html = this.opts.emptyHtml;
21585					}
21586
21587					html = html.replace(/<p><span id="selection-marker-1" class="redactor-selection-marker">​<\/span><\/p>/, '');
21588
21589					this.events.stopDetectChanges();
21590					this.core.editor().html(html);
21591					this.observe.load();
21592					this.events.startDetectChanges();
21593				},
21594				set: function(html, options)
21595				{
21596					html = $.trim(html);
21597
21598                    options = options || {};
21599
21600                    // start
21601                    if (options.start)
21602                    {
21603                        this.start = options.start;
21604                    }
21605
21606					// clean
21607					if (this.opts.type === 'textarea')
21608					{
21609						html = this.clean.onSet(html);
21610					}
21611					else if (this.opts.type === 'div' && html === '')
21612					{
21613						html = this.opts.emptyHtml;
21614					}
21615
21616					this.core.editor().html(html);
21617
21618					if (this.opts.type === 'textarea')
21619					{
21620    					this.code.sync();
21621					}
21622
21623					this.placeholder.enable();
21624				},
21625				get: function()
21626				{
21627					if (this.opts.type === 'textarea')
21628					{
21629						return this.core.textarea().val();
21630                    }
21631					else
21632					{
21633						var html = this.core.editor().html();
21634
21635						// clean
21636						html = this.clean.onGet(html);
21637
21638						return html;
21639					}
21640				},
21641				sync: function()
21642				{
21643					if (!this.code.syncFire)
21644					{
21645						return;
21646					}
21647
21648					var html = this.core.editor().html();
21649					var htmlCleaned = this.code.cleaned(html);
21650
21651					// is there a need to synchronize
21652					if (this.code.isSync(htmlCleaned))
21653					{
21654						// do not sync
21655						return;
21656					}
21657
21658					// save code
21659					this.code.html = htmlCleaned;
21660
21661					if (this.opts.type !== 'textarea')
21662					{
21663						this.core.callback('sync', html);
21664						this.core.callback('change', html);
21665						return;
21666					}
21667
21668					if (this.opts.type === 'textarea')
21669					{
21670						setTimeout($.proxy(function()
21671						{
21672							this.code.startSync(html);
21673
21674						}, this), 10);
21675					}
21676				},
21677				startSync: function(html)
21678				{
21679					// before clean callback
21680					html = this.core.callback('syncBefore', html);
21681
21682					// clean
21683					html = this.clean.onSync(html);
21684
21685					// set code
21686					this.core.textarea().val(html);
21687
21688					// after sync callback
21689					this.core.callback('sync', html);
21690
21691					// change callback
21692					if (this.start === false)
21693					{
21694						this.core.callback('change', html);
21695					}
21696
21697					this.start = false;
21698				},
21699				isSync: function(htmlCleaned)
21700				{
21701					var html = (this.code.html !== false) ? this.code.html : false;
21702
21703					return (html !== false && html === htmlCleaned);
21704				},
21705				cleaned: function(html)
21706				{
21707					html = html.replace(/\u200B/g, '');
21708					return this.clean.removeMarkers(html);
21709				}
21710			};
21711		},
21712
21713		// =core
21714		core: function()
21715		{
21716			return {
21717
21718				id: function()
21719				{
21720					return this.$editor.attr('id');
21721				},
21722				element: function()
21723				{
21724					return this.$element;
21725				},
21726				editor: function()
21727				{
21728					return (typeof this.$editor === 'undefined') ? $() : this.$editor;
21729				},
21730				textarea: function()
21731				{
21732					return this.$textarea;
21733				},
21734				box: function()
21735				{
21736					return (this.opts.type === 'textarea') ? this.$box : this.$element;
21737				},
21738				toolbar: function()
21739				{
21740					return (this.$toolbar) ? this.$toolbar : false;
21741				},
21742				air: function()
21743				{
21744					return (this.$air) ? this.$air : false;
21745				},
21746				object: function()
21747				{
21748					return $.extend({}, this);
21749				},
21750				structure: function()
21751				{
21752					this.core.editor().toggleClass('redactor-structure');
21753				},
21754				addEvent: function(name)
21755				{
21756					this.core.event = name;
21757				},
21758				getEvent: function()
21759				{
21760					return this.core.event;
21761				},
21762				callback: function(type, e, data)
21763				{
21764					var eventNamespace = 'redactor';
21765					var returnValue = false;
21766					var events = $._data(this.core.element()[0], 'events');
21767
21768					// on callback
21769					if (typeof events !== 'undefined' && typeof events[type] !== 'undefined')
21770					{
21771						var len = events[type].length;
21772						for (var i = 0; i < len; i++)
21773						{
21774							var namespace = events[type][i].namespace;
21775							if (namespace === 'callback.' + eventNamespace)
21776							{
21777								var handler = events[type][i].handler;
21778								var args = (typeof data === 'undefined') ? [e] : [e, data];
21779								returnValue = (typeof args === 'undefined') ? handler.call(this, e) : handler.call(this, e, args);
21780							}
21781						}
21782					}
21783
21784					if (returnValue)
21785					{
21786						return returnValue;
21787					}
21788
21789					// no callback
21790					if (typeof this.opts.callbacks[type] === 'undefined')
21791					{
21792						return (typeof data === 'undefined') ? e : data;
21793					}
21794
21795					// callback
21796					var callback = this.opts.callbacks[type];
21797
21798					if ($.isFunction(callback))
21799					{
21800						return (typeof data === 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
21801					}
21802					else
21803					{
21804						return (typeof data === 'undefined') ? e : data;
21805					}
21806				},
21807				destroy: function()
21808				{
21809					this.opts.destroyed = true;
21810
21811					this.core.callback('destroy');
21812
21813					// placeholder
21814					this.placeholder.destroy();
21815
21816					// progress
21817					this.progress.destroy();
21818
21819					// help label
21820					$('#redactor-voice-' + this.uuid).remove();
21821
21822					this.core.editor().removeClass('redactor-in redactor-styles redactor-structure redactor-layer-img-edit');
21823
21824					// caret service
21825					this.core.editor().off('keydown.redactor-remove-textnode');
21826
21827					// observer
21828					this.core.editor().off('.redactor-observe.' + this.uuid);
21829
21830					// off events and remove data
21831					this.$element.off('.redactor').removeData('redactor');
21832					this.core.editor().off('.redactor');
21833
21834					$(document).off('.redactor-dropdown');
21835					$(document).off('.redactor-air.' + this.uuid);
21836					$(document).off('mousedown.redactor-blur.' + this.uuid);
21837					$(document).off('mousedown.redactor.' + this.uuid);
21838					$(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid);
21839					$(window).off('.redactor-toolbar.' + this.uuid);
21840					$(window).off('touchmove.redactor.' + this.uuid);
21841					$("body").off('scroll.redactor.' + this.uuid);
21842
21843					$(this.opts.toolbarFixedTarget).off('scroll.redactor.' + this.uuid);
21844
21845					// plugins events
21846					var self = this;
21847					if (this.opts.plugins !== false)
21848					{
21849						$.each(this.opts.plugins, function(i,s)
21850						{
21851							$(window).off('.redactor-plugin-' + s);
21852							$(document).off('.redactor-plugin-' + s);
21853							$("body").off('.redactor-plugin-' + s);
21854							self.core.editor().off('.redactor-plugin-' + s);
21855						});
21856					}
21857
21858					// click to edit
21859					this.$element.off('click.redactor-click-to-edit');
21860					this.$element.removeClass('redactor-click-to-edit');
21861
21862					// common
21863					this.core.editor().removeClass('redactor-layer');
21864					this.core.editor().removeAttr('contenteditable');
21865
21866					var html = this.code.get();
21867
21868					if (this.opts.toolbar && this.$toolbar)
21869					{
21870						// dropdowns off
21871						this.$toolbar.find('a').each(function()
21872						{
21873							var $el = $(this);
21874							if ($el.data('dropdown'))
21875							{
21876								$el.data('dropdown').remove();
21877								$el.data('dropdown', {});
21878							}
21879						});
21880					}
21881
21882					if (this.opts.type === 'textarea')
21883					{
21884						this.$box.after(this.$element);
21885						this.$box.remove();
21886						this.$element.val(html).show();
21887					}
21888
21889					// air
21890					if (this.opts.air)
21891					{
21892						this.$air.remove();
21893					}
21894
21895					if (this.opts.toolbar && this.$toolbar)
21896					{
21897						this.$toolbar.remove();
21898					}
21899
21900					// modal
21901					if (this.$modalBox)
21902					{
21903						this.$modalBox.remove();
21904					}
21905
21906					if (this.$modalOverlay)
21907					{
21908						this.$modalOverlay.remove();
21909					}
21910
21911					// hide link's tooltip
21912					$('.redactor-link-tooltip').remove();
21913
21914					// autosave
21915					clearInterval(this.autosaveTimeout);
21916				}
21917			};
21918		},
21919
21920		// =detect
21921		detect: function()
21922		{
21923			return {
21924
21925				// public
21926				isWebkit: function()
21927				{
21928					return /webkit/.test(this.opts.userAgent);
21929				},
21930				isFirefox: function()
21931				{
21932					return this.opts.userAgent.indexOf('firefox') > -1;
21933				},
21934				isIe: function(v)
21935				{
21936                    if (document.documentMode || /Edge/.test(navigator.userAgent))
21937                    {
21938                        return 'edge';
21939                    }
21940
21941					var ie;
21942					ie = RegExp('msie' + (!isNaN(v)?('\\s'+v):''), 'i').test(navigator.userAgent);
21943
21944					if (!ie)
21945					{
21946						ie = !!navigator.userAgent.match(/Trident.*rv[ :]*11\./);
21947					}
21948
21949					return ie;
21950				},
21951				isMobile: function()
21952				{
21953					return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
21954				},
21955				isDesktop: function()
21956				{
21957					return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent);
21958				},
21959				isIpad: function()
21960				{
21961					return /iPad/.test(navigator.userAgent);
21962				}
21963
21964			};
21965		},
21966
21967		// =dropdown
21968		dropdown: function()
21969		{
21970			return {
21971				active: false,
21972				button: false,
21973				key: false,
21974				position: [],
21975				getDropdown: function()
21976				{
21977					return this.dropdown.active;
21978				},
21979				build: function(name, $dropdown, dropdownObject)
21980				{
21981					dropdownObject = this.dropdown.buildFormatting(name, dropdownObject);
21982
21983					$.each(dropdownObject, $.proxy(function(btnName, btnObject)
21984					{
21985						var $item = this.dropdown.buildItem(btnName, btnObject);
21986
21987						this.observe.addDropdown($item, btnName, btnObject);
21988						$dropdown.attr('rel', name).append($item);
21989
21990					}, this));
21991				},
21992				buildFormatting: function(name, dropdownObject)
21993				{
21994					if (name !== 'format' || this.opts.formattingAdd === false)
21995					{
21996						return dropdownObject;
21997					}
21998
21999					$.each(this.opts.formattingAdd, $.proxy(function(i,s)
22000					{
22001						var type = (this.utils.isBlockTag(s.args[0])) ? 'block' : 'inline';
22002
22003						dropdownObject[i] = {
22004							func: (type === 'block') ? 'block.format' : 'inline.format',
22005							args: s.args,
22006							title: s.title
22007						};
22008
22009					}, this));
22010
22011					return dropdownObject;
22012				},
22013				buildItem: function(btnName, btnObject)
22014				{
22015					var $itemContainer = $('<li />');
22016					if (typeof btnObject.classname !== 'undefined')
22017					{
22018    					$itemContainer.addClass(btnObject.classname);
22019					}
22020
22021					if (btnName.search(/^divider/i) !== -1)
22022					{
22023    					$itemContainer.addClass('redactor-dropdown-divider');
22024
22025    					return $itemContainer;
22026					}
22027
22028					var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '" role="button" />');
22029					var $itemSpan = $('<span />').html(btnObject.title);
22030
22031					$item.append($itemSpan);
22032					$item.on('mousedown', $.proxy(function(e)
22033					{
22034						e.preventDefault();
22035
22036						this.dropdown.buildClick(e, btnName, btnObject);
22037
22038					}, this));
22039
22040					$itemContainer.append($item);
22041
22042					return $itemContainer;
22043
22044				},
22045				buildClick: function(e, btnName, btnObject)
22046				{
22047					if ($(e.target).hasClass('redactor-dropdown-link-inactive'))
22048					{
22049						return;
22050					}
22051
22052					var command = this.dropdown.buildCommand(btnObject);
22053
22054					if (typeof btnObject.args !== 'undefined')
22055					{
22056						this.button.toggle(e, btnName, command.type, command.callback, btnObject.args);
22057					}
22058					else
22059					{
22060						this.button.toggle(e, btnName, command.type, command.callback);
22061					}
22062				},
22063				buildCommand: function(btnObject)
22064				{
22065					var command = {};
22066					command.type = 'func';
22067					command.callback = btnObject.func;
22068
22069					if (btnObject.command)
22070					{
22071						command.type = 'command';
22072						command.callback = btnObject.command;
22073					}
22074					else if (btnObject.dropdown)
22075					{
22076						command.type = 'dropdown';
22077						command.callback = btnObject.dropdown;
22078					}
22079
22080					return command;
22081				},
22082				show: function(e, key)
22083				{
22084					if (this.detect.isDesktop())
22085					{
22086						this.core.editor().focus();
22087					}
22088
22089					this.dropdown.hideAll(false, key);
22090
22091					this.dropdown.key = key;
22092					this.dropdown.button = this.button.get(this.dropdown.key);
22093
22094					if (this.dropdown.button.hasClass('dropact'))
22095					{
22096						this.dropdown.hide();
22097						return;
22098					}
22099
22100					// re append
22101					this.dropdown.active = this.dropdown.button.data('dropdown').appendTo(document.body);
22102
22103					// callback
22104					this.core.callback('dropdownShow', { dropdown: this.dropdown.active, key: this.dropdown.key, button: this.dropdown.button });
22105
22106					// set button
22107					this.button.setActive(this.dropdown.key);
22108					this.dropdown.button.addClass('dropact');
22109
22110					// position
22111					this.dropdown.getButtonPosition();
22112
22113					// show
22114					if (this.button.toolbar().hasClass('toolbar-fixed-box') && this.detect.isDesktop())
22115					{
22116						this.dropdown.showIsFixedToolbar();
22117					}
22118					else
22119					{
22120						this.dropdown.showIsUnFixedToolbar();
22121					}
22122
22123					// disable scroll whan dropdown scroll
22124					if (this.detect.isDesktop() && !this.detect.isFirefox())
22125					{
22126						this.dropdown.active.on('mouseover.redactor-dropdown', $.proxy(this.utils.disableBodyScroll, this));
22127						this.dropdown.active.on('mouseout.redactor-dropdown mousedown.redactor-dropdown', $.proxy(this.utils.enableBodyScroll, this));
22128					}
22129
22130					e.stopPropagation();
22131
22132				},
22133				showIsFixedToolbar: function()
22134				{
22135					var top = this.dropdown.button.position().top + this.dropdown.button.innerHeight() + this.opts.toolbarFixedTo
22135pOffset;
22136
22137					var position = 'fixed';
22138					if (this.opts.toolbarFixedTarget !== document)
22139					{
22140						top = (this.dropdown.button.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
22141						position = 'absolute';
22142					}
22143
22144					this.dropdown.active.css({
22145
22146						position: position,
22147						left: this.dropdown.position.left + 'px',
22148						top: top + 'px'
22149
22150					}).show();
22151
22152					// animate
22153					this.dropdown.active.redactorAnimation('slideDown', { duration: 0.2 }, $.proxy(function()
22154					{
22155						this.dropdown.enableCallback();
22156						this.dropdown.enableEvents();
22157
22158					}, this));
22159				},
22160				showIsUnFixedToolbar: function()
22161				{
22162					this.dropdown.active.css({
22163
22164						position: 'absolute',
22165						left: this.dropdown.position.left + 'px',
22166						top: (this.dropdown.button.innerHeight() + this.dropdown.position.top) + 'px'
22167
22168					}).show();
22169
22170					// animate
22171					this.dropdown.active.redactorAnimation(((this.opts.animation) ? 'slideDown' : 'show'), { duration: 0.2 }, $.proxy(function()
22172					{
22173						this.dropdown.enableCallback();
22174						this.dropdown.enableEvents();
22175
22176					}, this));
22177				},
22178				enableEvents: function()
22179				{
22180					$(document).on('mousedown.redactor-dropdown', $.proxy(this.dropdown.hideAll, this));
22181					this.core.editor().on('touchstart.redactor-dropdown', $.proxy(this.dropdown.hideAll, this));
22182					$(document).on('keyup.redactor-dropdown', $.proxy(this.dropdown.closeHandler, this));
22183				},
22184				enableCallback: function()
22185				{
22186					this.core.callback('dropdownShown', { dropdown: this.dropdown.active, key: this.dropdown.key, button: this.dropdown.button });
22187				},
22188				getButtonPosition: function()
22189				{
22190					this.dropdown.position = this.dropdown.button.offset();
22191
22192					// fix right placement
22193					var dropdownWidth = this.dropdown.active.width();
22194					if ((this.dropdown.position.left + dropdownWidth) > $(document).width())
22195					{
22196						this.dropdown.position.left = Math.max(0, this.dropdown.position.left - dropdownWidth + parseInt(this.dropdown.button.innerWidth()));
22197					}
22198
22199				},
22200				closeHandler: function(e)
22201				{
22202					if (e.which !== this.keyCode.ESC)
22203					{
22204						return;
22205					}
22206
22207					this.dropdown.hideAll(e);
22208					this.core.editor().focus();
22209				},
22210				hideAll: function(e, key)
22211				{
22212					if (this.detect.isDesktop())
22213					{
22214						this.utils.enableBodyScroll();
22215					}
22216
22217					if (e !== false && $(e.target).closest('.redactor-dropdown').length !== 0)
22218					{
22219						return;
22220					}
22221
22222					var $buttons = (typeof key === 'undefined') ? this.button.toolbar().find('a.dropact') : this.button.toolbar().find('a.dropact').not('.re-' + key);
22223					var $elements = (typeof key === 'undefined') ? $('.redactor-dropdown-' + this.uuid) : $('.redactor-dropdown-' + this.uuid).not('.redactor-dropdown-box-' + key);
22224
22225					if ($elements.length !== 0)
22226					{
22227						$(document).off('.redactor-dropdown');
22228						this.core.editor().off('.redactor-dropdown');
22229
22230						$.each($elements, $.proxy(function(i,s)
22231						{
22232							var $el = $(s);
22233
22234							this.core.callback('dropdownHide', $el);
22235
22236							$el.hide();
22237							$el.off('mouseover mouseout').off('.redactor-dropdown');
22238
22239						}, this));
22240
22241						$buttons .removeClass('redactor-act dropact');
22242					}
22243
22244				},
22245				hide: function ()
22246				{
22247					if (this.dropdown.active === false)
22248					{
22249						return;
22250					}
22251
22252					if (this.detect.isDesktop())
22253					{
22254						this.utils.enableBodyScroll();
22255					}
22256
22257					this.dropdown.active.redactorAnimation(((this.opts.animation) ? 'slideUp' : 'hide'), { duration: 0.2 }, $.proxy(function()
22258					{
22259						$(document).off('.redactor-dropdown');
22260						this.core.editor().off('.redactor-dropdown');
22261
22262						this.dropdown.hideOut();
22263
22264
22265					}, this));
22266				},
22267				hideOut: function()
22268				{
22269					this.core.callback('dropdownHide', this.dropdown.active);
22270
22271					this.dropdown.button.removeClass('redactor-act dropact');
22272					this.dropdown.active.off('mouseover mouseout').off('.redactor-dropdown');
22273					this.dropdown.button = false;
22274					this.dropdown.key = false;
22275					this.dropdown.active = false;
22276				}
22277			};
22278		},
22279
22280		// =events
22281		events: function()
22282		{
22283			return {
22284				focused: false,
22285				blured: true,
22286				dropImage: false,
22287				stopChanges: false,
22288				stopDetectChanges: function()
22289				{
22290					this.events.stopChanges = true;
22291				},
22292				startDetectChanges: function()
22293				{
22294					var self = this;
22295					setTimeout(function()
22296					{
22297						self.events.stopChanges = false;
22298					}, 1);
22299				},
22300				dragover: function(e)
22301				{
22302					e.preventDefault();
22303					e.stopPropagation();
22304
22305					if (e.target.tagName === 'IMG')
22306					{
22307						$(e.target).addClass('redactor-image-dragover');
22308					}
22309
22310				},
22311				dragleave: function(e)
22312				{
22313					// remove image dragover
22314					this.core.editor().find('img').removeClass('redactor-image-dragover');
22315				},
22316				drop: function(e)
22317				{
22318					e = e.originalEvent || e;
22319
22320					// remove image dragover
22321					this.core.editor().find('img').removeClass('redactor-image-dragover');
22322
22323					if (this.opts.type === 'inline' || this.opts.type === 'pre')
22324					{
22325						e.preventDefault();
22326						return false;
22327					}
22328
22329					if (window.FormData === undefined || !e.dataTransfer)
22330					{
22331						return true;
22332					}
22333
22334					if (e.dataTransfer.files.length === 0)
22335					{
22336						return this.events.onDrop(e);
22337					}
22338					else
22339					{
22340						this.events.onDropUpload(e);
22341					}
22342
22343					this.core.callback('drop', e);
22344
22345				},
22346				click: function(e)
22347				{
22348					var event = this.core.getEvent();
22349					var type = (event === 'click' || event === 'arrow') ? false : 'click';
22350
22351                    this.core.addEvent(type);
22352					this.utils.disableSelectAll();
22353					this.core.callback('click', e);
22354				},
22355				focus: function(e)
22356				{
22357					if (this.rtePaste)
22358					{
22359						return;
22360					}
22361
22362					if (this.events.isCallback('focus'))
22363					{
22364						this.core.callback('focus', e);
22365					}
22366
22367					this.events.focused = true;
22368					this.events.blured = false;
22369
22370					// tab
22371					if (this.selection.current() === false)
22372					{
22373						var sel = this.selection.get();
22374						var range = this.selection.range(sel);
22375
22376						range.setStart(this.core.editor()[0], 0);
22377						range.setEnd(this.core.editor()[0], 0);
22378						this.selection.update(sel, range);
22379					}
22380
22381				},
22382				blur: function(e)
22383				{
22384					if (this.start || this.rtePaste)
22385					{
22386						return;
22387					}
22388
22389					if ($(e.target).closest('#' + this.core.id() + ', .redactor-toolbar, .redactor-dropdown, #redactor-modal-box').length !== 0)
22390					{
22391						return;
22392					}
22393
22394					if (!this.events.blured && this.events.isCallback('blur'))
22395					{
22396						this.core.callback('blur', e);
22397					}
22398
22399					this.events.focused = false;
22400					this.events.blured = true;
22401				},
22402				touchImageEditing: function()
22403				{
22404					var scrollTimer = -1;
22405					this.events.imageEditing = false;
22406					$(window).on('touchmove.redactor.' + this.uuid, $.proxy(function()
22407					{
22408						this.events.imageEditing = true;
22409						if (scrollTimer !== -1)
22410						{
22411							clearTimeout(scrollTimer);
22412						}
22413
22414						scrollTimer = setTimeout($.proxy(function()
22415						{
22416							this.events.imageEditing = false;
22417
22418						}, this), 500);
22419
22420					}, this));
22421				},
22422				init: function()
22423				{
22424					this.core.editor().on('dragover.redactor dragenter.redactor', $.proxy(this.events.dragover, this));
22425					this.core.editor().on('dragleave.redactor', $.proxy(this.events.dragleave, this));
22426					this.core.editor().on('drop.redactor', $.proxy(this.events.drop, this));
22427					this.core.editor().on('click.redactor', $.proxy(this.events.click, this));
22428					this.core.editor().on('paste.redactor', $.proxy(this.paste.init, this));
22429					this.core.editor().on('keydown.redactor', $.proxy(this.keydown.init, this));
22430					this.core.editor().on('keyup.redactor', $.proxy(this.keyup.init, this));
22431					this.core.editor().on('focus.redactor', $.proxy(this.events.focus, this));
22432
22433					$(document).on('mousedown.redactor-blur.' + this.uuid, $.proxy(this.events.blur, this));
22434
22435					this.events.touchImageEditing();
22436
22437					this.events.createObserver();
22438					this.events.setupObserver();
22439
22440				},
22441				createObserver: function()
22442				{
22443					var self = this;
22444					this.events.observer = new MutationObserver(function(mutations)
22445					{
22446						mutations.forEach($.proxy(self.events.iterateObserver, self));
22447					});
22448
22449				},
22450				iterateObserver: function(mutation)
22451				{
22452
22453					var stop = false;
22454
22455					// target
22456					if (((this.opts.type === 'textarea' || this.opts.type === 'div')
22457					    && (!this.detect.isFirefox() && mutation.target === this.core.editor()[0]))
22458					    || (mutation.attributeName === 'class' && mutation.target === this.core.editor()[0])
22459					    || (mutation.attributeName == 'data-vivaldi-spatnav-clickable')
22460                    )
22461					{
22462						stop = true;
22463					}
22464
22465					if (!stop)
22466					{
22467    					this.observe.load();
22468						this.events.changeHandler();
22469					}
22470				},
22471				setupObserver: function()
22472				{
22473					this.events.observer.observe(this.core.editor()[0], {
22474						 attributes: true,
22475						 subtree: true,
22476						 childList: true,
22477						 characterData: true,
22478						 characterDataOldValue: true
22479					});
22480				},
22481				changeHandler: function()
22482				{
22483					if (this.events.stopChanges)
22484					{
22485						return;
22486					}
22487
22488					this.code.sync();
22489
22490					// autosave
22491					if (this.autosave.is())
22492					{
22493
22494						clearTimeout(this.autosaveTimeout);
22495						this.autosaveTimeout = setTimeout($.proxy(this.autosave.send, this), 300);
22496					}
22497
22498				},
22499				onDropUpload: function(e)
22500				{
22501					e.preventDefault();
22502					e.stopPropagation();
22503
22504					if ((!this.opts.dragImageUpload && !this.opts.dragFileUpload) || (this.opts.imageUpload === null && this.opts.fileUpload === null))
22505					{
22506						return;
22507					}
22508
22509					if (e.target.tagName === 'IMG')
22510					{
22511						this.events.dropImage = e.target;
22512					}
22513
22514					var files = e.dataTransfer.files;
22515                    var len = files.length;
22516					for (var i = 0; i < len; i++)
22517					{
22518    					this.upload.directUpload(files[i], e);
22519    				}
22520				},
22521				onDrop: function(e)
22522				{
22523					this.core.callback('drop', e);
22524				},
22525				isCallback: function(name)
22526				{
22527					return (typeof this.opts.callbacks[name] !== 'undefined' && $.isFunction(this.opts.callbacks[name]));
22528				},
22529
22530				// #backward
22531				stopDetect: function()
22532				{
22533					this.events.stopDetectChanges();
22534				},
22535				startDetect: function()
22536				{
22537					this.events.startDetectChanges();
22538				}
22539
22540			};
22541		},
22542
22543		// =file
22544		file: function()
22545		{
22546			return {
22547				is: function()
22548				{
22549					return !(!this.opts.fileUpload || !this.opts.fileUpload && !this.opts.s3);
22550				},
22551				show: function()
22552				{
22553					// build modal
22554					this.modal.load('file', this.lang.get('file'), 700);
22555
22556					// build upload
22557					this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert);
22558
22559					// set selected text
22560					$('#redactor-filename').val(this.selection.get().toString());
22561
22562					// show
22563					this.modal.show();
22564				},
22565				insert: function(json, direct, e)
22566				{
22567					// error callback
22568					if (typeof json.error !== 'undefined')
22569					{
22570						this.modal.close();
22571						this.core.callback('fileUploadError', json);
22572						return;
22573					}
22574
22575					this.file.release(e, direct);
22576
22577					// prepare
22578					this.buffer.set();
22579					this.air.collapsed();
22580
22581					// get
22582					var text = this.file.text(json);
22583					var $link = $('<a />').attr('href', json.url).text(text);
22584					var id = (typeof json.id === 'undefined') ? '' : json.id;
22585					var type = (typeof json.s3 === 'undefined') ? 'file' : 's3';
22586
22587					// set id
22588					$link.attr('data-' + type, id);
22589
22590					// insert
22591					$link = $(this.insert.node($link));
22592
22593					// focus
22594					this.caret.after($link);
22595
22596					// callback
22597					this.storage.add({ type: type, node: $link[0], url: json.url, id: id });
22598
22599					if (direct !== null)
22600					{
22601						this.core.callback('fileUpload', $link, json);
22602					}
22603
22604				},
22605				release: function(e, direct)
22606				{
22607					if (direct)
22608					{
22609						// drag and drop upload
22610						this.marker.remove();
22611						this.insert.nodeToPoint(e, this.marker.get());
22612						this.selection.restore();
22613					}
22614					else
22615					{
22616						// upload from modal
22617						this.modal.close();
22618					}
22619				},
22620				text: function(json)
22621				{
22622					var text = $('#redactor-filename').val();
22623
22624					return (typeof text === 'undefined' || text === '') ? json.name : text;
22625				}
22626			};
22627		},
22628
22629		// =focus
22630		focus: function()
22631		{
22632			return {
22633				start: function()
22634				{
22635					this.core.editor().focus();
22636
22637					if (this.opts.type === 'inline')
22638					{
22639						return;
22640					}
22641
22642					var $first = this.focus.first();
22643					if ($first !== false)
22644					{
22645						this.caret.start($first);
22646					}
22647				},
22648				end: function()
22649				{
22650					this.core.editor().focus();
22651
22652					var last = (this.opts.inline) ? this.core.editor() : this.focus.last();
22653					if (last.length === 0)
22654					{
22655						return;
22656					}
22657
22658					// get inline last node
22659					var lastNode = this.focus.lastChild(last);
22660					if (!this.detect.isWebkit() && lastNode !== false)
22661					{
22662						this.caret.end(lastNode);
22663					}
22664					else
22665					{
22666						var sel = this.selection.get();
22667						var range = this.selection.range(sel);
22668
22669						if (range !== null)
22670						{
22671							range.selectNodeContents(last[0]);
22672							range.collapse(false);
22673
22674							this.selection.update(sel, range);
22675						}
22676						else
22677						{
22678							this.caret.end(last);
22679						}
22680					}
22681
22682				},
22683				first: function()
22684				{
22685					var $first = this.core.editor().children().first();
22686					if ($first.length === 0 && ($first[0].length === 0 || $first[0].tagName === 'BR' || $first[0].tagName === 'HR' || $first[0].nodeType === 3))
22687					{
22688						return false;
22689					}
22690
22691					if ($first[0].tagName === 'UL' || $first[0].tagName === 'OL')
22692					{
22693						return $first.find('li').first();
22694					}
22695
22696					return $first;
22697
22698				},
22699				last: function()
22700				{
22701					return this.core.editor().children().last();
22702				},
22703				lastChild: function(last)
22704				{
22705					var lastNode = last[0].lastChild;
22706
22707					return (lastNode !== null && this.utils.isInlineTag(lastNode.tagName)) ? lastNode : false;
22708				},
22709				is: function()
22710				{
22711					return (this.core.editor()[0] === document.activeElement);
22712				}
22713			};
22714		},
22715
22716		// =image
22717		image: function()
22718		{
22719			return {
22720				is: function()
22721				{
22722					return !(!this.opts.imageUpload || !this.opts.imageUpload && !this.opts.s3);
22723				},
22724				show: function()
22725				{
22726					// build modal
22727					this.modal.load('image', this.lang.get('image'), 700);
22728
22729					// build upload
22730					this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
22731					this.modal.show();
22732
22733				},
22734				insert: function(json, direct, e)
22735				{
22736					var $img;
22737
22738					// error callback
22739					if (typeof json.error !== 'undefined')
22740					{
22741						this.modal.close();
22742						this.events.dropImage = false;
22743						this.core.callback('imageUploadError', json, e);
22744						return;
22745					}
22746
22747					// change image
22748					if (this.events.dropImage !== false)
22749					{
22750						$img = $(this.events.dropImage);
22751
22752						this.core.callback('imageDelete', $img[0].src, $img);
22753
22754						$img.attr('src', json.url);
22755
22756						this.events.dropImage = false;
22757						this.core.callback('imageUpload', $img, json);
22758						return;
22759					}
22760
22761					this.placeholder.hide();
22762					var $figure = $('<' + this.opts.imageTag + '>');
22763
22764					$img = $('<img>');
22765					$img.attr('src', json.url);
22766
22767					// set id
22768					var id = (typeof json.id === 'undefined') ? '' : json.id;
22769					var type = (typeof json.s3 === 'undefined') ? 'image' : 's3';
22770					$img.attr('data-' + type, id);
22771
22772					$figure.append($img);
22773
22774					var pre = this.utils.isTag(this.selection.current(), 'pre');
22775
22776					if (direct)
22777					{
22778						this.air.collapsed();
22779						this.marker.remove();
22780
22781						var node = this.insert.nodeToPoint(e, this.marker.get());
22782						var $next = $(node).next();
22783
22784						this.selection.restore();
22785
22786						// buffer
22787						this.buffer.set();
22788
22789						// insert
22790						if (typeof $next !== 'undefined' && $next.length !== 0 && $next[0].tagName === 'IMG')
22791						{
22792							// delete callback
22793							this.core.callback('imageDelete', $next[0].src, $next);
22794
22795							// replace
22796							$next.closest('figure, p', this.core.editor()[0]).replaceWith($figure);
22797							this.caret.after($figure);
22798						}
22799						else
22800						{
22801							if (pre)
22802							{
22803								$(pre).after($figure);
22804							}
22805							else
22806							{
22807								this.insert.node($figure);
22808							}
22809
22810							this.caret.after($figure);
22811						}
22812
22813					}
22814					else
22815					{
22816						this.modal.close();
22817
22818						// buffer
22819						this.buffer.set();
22820
22821						// insert
22822						this.air.collapsed();
22823
22824						if (pre)
22825						{
22826							$(pre).after($figure);
22827						}
22828						else
22829						{
22830							this.insert.node($figure);
22831						}
22832
22833						this.caret.after($figure);
22834					}
22835
22836					this.events.dropImage = false;
22837					this.storage.add({ type: type, node: $img[0], url: json.url, id: id });
22838
22839					var nextNode = $img[0].nextSibling;
22840					var $nextFigure = $figure.next();
22841					var isNextEmpty = $(nextNode).text().replace(/\u200B/g, '');
22842					var isNextFigureEmpty = $nextFigure.text().replace(/\u200B/g, '');
22843
22844					if (isNextEmpty === '')
22845					{
22846    					$(nextNode).remove();
22847					}
22848
22849					if ($nextFigure.length === 1 && $nextFigure[0].tagName === 'FIGURE' && isNextFigureEmpty === '')
22850					{
22851    					$nextFigure.remove();
22852					}
22853
22854					if (direct !== null)
22855					{
22856						this.core.callback('imageUpload', $img, json);
22857					}
22858					else
22859					{
22860    					this.core.callback('imageInserted', $img, json);
22861					}
22862				},
22863				setEditable: function($image)
22864				{
22865					$image.on('dragstart', function(e)
22866					{
22867						e.preventDefault();
22868					});
22869
22870                    if (this.opts.imageResizable)
22871                    {
22872    					var handler = $.proxy(function(e)
22873    					{
22874    						this.observe.image = $image;
22875    						this.image.resizer = this.image.loadEditableControls($image);
22876
22877    						$(document).on('mousedown.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this));
22878
22879    						if (this.image.resizer)
22880    						{
22881                                this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
22882        						{
22883        							this.image.setResizable(e, $image);
22884        						}, this));
22885    						}
22886
22887    					}, this);
22888
22889    					$image.off('mousedown.redactor').on('mousedown.redactor', $.proxy(this.image.hideResize, this));
22890    					$image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', handler);
22891                    }
22892                    else
22893                    {
22894    					$image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', $.proxy(function(e)
22895    					{
22896    						setTimeout($.proxy(function()
22897    						{
22898    							this.image.showEdit($image);
22899
22900    						}, this), 200);
22901
22902    					}, this));
22903                    }
22904
22905				},
22906				setResizable: function(e, $image)
22907				{
22908					e.preventDefault();
22909
22910				    this.image.resizeHandle = {
22911				        x : e.pageX,
22912				        y : e.pageY,
22913				        el : $image,
22914				        ratio: $image.width() / $image.height(),
22915				        h: $image.height()
22916				    };
22917
22918				    e = e.originalEvent || e;
22919
22920				    if (e.targetTouches)
22921				    {
22922				         this.image.resizeHandle.x = e.targetTouches[0].pageX;
22923				         this.image.resizeHandle.y = e.targetTouches[0].pageY;
22924				    }
22925
22926					this.image.startResize();
22927				},
22928				startResize: function()
22929				{
22930					$(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
22931					$(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
22932				},
22933				moveResize: function(e)
22934				{
22935					e.preventDefault();
22936
22937					e = e.originalEvent || e;
22938
22939					var height = this.image.resizeHandle.h;
22940
22941		            if (e.targetTouches) height += (e.targetTouches[0].pageY -  this.image.resizeHandle.y);
22942		            else height += (e.pageY -  this.image.resizeHandle.y);
22943
22944					var width = Math.round(height * this.image.resizeHandle.ratio);
22945
22946					if (height < 50 || width < 100) return;
22947					if (this.core.editor().width() <= width) return;
22948
22949					this.image.resizeHandle.el.attr({width: width, height: height});
22950		            this.image.resizeHandle.el.width(width);
22951		            this.image.resizeHandle.el.height(height);
22952
22953		            this.code.sync();
22954				},
22955				stopResize: function()
22956				{
22957					this.handle = false;
22958					$(document).off('.redactor-image-resize');
22959
22960					this.image.hideResize();
22961				},
22962                hideResize: function(e)
22963				{
22964					if (e && $(e.target).closest('#redactor-image-box', this.$editor[0]).length !== 0) return;
22965					if (e && e.target.tagName == 'IMG')
22966					{
22967						var $image = $(e.target);
22968					}
22969
22970					var imageBox = this.$editor.find('#redactor-image-box');
22971					if (imageBox.length === 0) return;
22972
22973					$('#redactor-image-editter').remove();
22974					$('#redactor-image-resizer').remove();
22975
22976					imageBox.find('img').css({
22977						marginTop: imageBox[0].style.marginTop,
22978						marginBottom: imageBox[0].style.marginBottom,
22979						marginLeft: imageBox[0].style.marginLeft,
22980						marginRight: imageBox[0].style.marginRight
22981					});
22982
22983					imageBox.css('margin', '');
22984					imageBox.find('img').css('opacity', '');
22985					imageBox.replaceWith(function()
22986					{
22987						return $(this).contents();
22988					});
22989
22990					$(document).off('mousedown.redactor-image-resize-hide.' + this.uuid);
22991
22992					if (typeof this.image.resizeHandle !== 'undefined')
22993					{
22994						this.image.resizeHandle.el.attr('rel', this.image.resizeHandle.el.attr('style'));
22995					}
22996				},
22997				loadResizableControls: function($image, imageBox)
22998				{
22999					if (this.opts.imageResizable && !this.detect.isMobile())
23000					{
23001						var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
23002
23003						if (!this.detect.isDesktop())
23004						{
23005							imageResizer.css({ width: '15px', height: '15px' });
23006						}
23007
23008						imageResizer.attr('contenteditable', false);
23009						imageBox.append(imageResizer);
23010						imageBox.append($image);
23011
23012						return imageResizer;
23013					}
23014					else
23015					{
23016						imageBox.append($image);
23017						return false;
23018					}
23019				},
23020				loadEditableControls: function($image)
23021				{
23022    				if ($('#redactor-image-box').length !== 0)
23023    				{
23024        				return;
23025    				}
23026
23027					var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
23028					imageBox.css('float', $image.css('float')).attr('contenteditable', false);
23029
23030					if ($image[0].style.margin != 'auto')
23031					{
23032						imageBox.css({
23033							marginTop: $image[0].style.marginTop,
23034							marginBottom: $image[0].style.marginBottom,
23035							marginLeft: $image[0].style.marginLeft,
23036							marginRight: $image[0].style.marginRight
23037						});
23038
23039						$image.css('margin', '');
23040					}
23041					else
23042					{
23043						imageBox.css({ 'display': 'block', 'margin': 'auto' });
23044					}
23045
23046					$image.css('opacity', '.5').after(imageBox);
23047
23048
23049					if (this.opts.imageEditable)
23050					{
23051						// editter
23052						this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>');
23053						this.image.editter.attr('contenteditable', false);
23054						this.image.editter.on('click', $.proxy(function()
23055						{
23056							this.image.showEdit($image);
23057						}, this));
23058
23059						imageBox.append(this.image.editter);
23060
23061						// position correction
23062						var editerWidth = this.image.editter.innerWidth();
23063						this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
23064					}
23065
23066					return this.image.loadResizableControls($image, imageBox);
23067
23068				},
23069				showEdit: function($image)
23070				{
23071					if (this.events.imageEditing)
23072					{
23073						return;
23074					}
23075
23076					this.observe.image = $image;
23077
23078					var $link = $image.closest('a', this.$editor[0]);
23079    				var $figure = $image.closest('figure', this.$editor[0]);
23080    				var $container = ($figure.length !== 0) ? $figure : $image;
23081
23082					this.modal.load('image-edit', this.lang.get('edit'), 705);
23083
23084					this.image.buttonDelete = this.modal.getDeleteButton().text(this.lang.get('delete'));
23085					this.image.buttonSave = this.modal.getActionButton().text(this.lang.get('save'));
23086
23087					this.image.buttonDelete.on('click', $.proxy(this.image.remove, this));
23088					this.image.buttonSave.on('click', $.proxy(this.image.update, this));
23089
23090					if (this.opts.imageCaption === false)
23091					{
23092						$('#redactor-image-caption').val('').hide().prev().hide();
23093					}
23094					else
23095					{
23096						var $parent = $image.closest(this.opts.imageTag, this.$editor[0]);
23097						var $ficaption = $parent.find('figcaption');
23098						if ($ficaption !== 0)
23099						{
23100
23101							$('#redactor-image-caption').val($ficaption.text()).show();
23102						}
23103					}
23104
23105					if (!this.opts.imagePosition)
23106					{
23107    					$('.redactor-image-position-option').hide();
23108    				}
23109					else
23110					{
23111    					var isCentered = ($figure.length !== 0) ? ($container.css('text-align') === 'center') : ($container.css('display') == 'block' && $container.css('float') == 'none');
23112						var floatValue = (isCentered) ? 'center' : $container.css('float');
23113						$('#redactor-image-align').val(floatValue);
23114					}
23115
23116					$('#redactor-image-preview').html($('<img src="' + $image.attr('src') + '" style="max-width: 100%;">'));
23117					$('#redactor-image-title').val($image.attr('alt'));
23118
23119					if ($link.length !== 0)
23120					{
23121						$('#redactor-image-link').val($link.attr('href'));
23122						if ($link.attr('target') === '_blank')
23123						{
23124							$('#redactor-image-link-blank').prop('checked', true);
23125						}
23126					}
23127
23128					// hide link's tooltip
23129					$('.redactor-link-tooltip').remove();
23130
23131					this.modal.show();
23132
23133					// focus
23134					if (this.detect.isDesktop())
23135					{
23136						$('#redactor-image-title').focus();
23137					}
23138
23139				},
23140				update: function()
23141				{
23142					var $image = this.observe.image;
23143					var $link = $image.closest('a', this.core.editor()[0]);
23144
23145					var title = $('#redactor-image-title').val().replace(/(<([^>]+)>)/ig,"");
23146					$image.attr('alt', title).attr('title', title);
23147
23148                    this.image.setFloating($image);
23149
23150					// as link
23151					var link = $.trim($('#redactor-image-link').val()).replace(/(<([^>]+)>)/ig,"");
23152					if (link !== '')
23153					{
23154						// test url (add protocol)
23155						var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
23156						var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
23157						var re2 = new RegExp('^' + pattern, 'i');
23158
23159						if (link.search(re) === -1 && link.search(re2) === 0 && this.opts.linkProtocol)
23160						{
23161							link = this.opts.linkProtocol + '://' + link;
23162						}
23163
23164						var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
23165
23166						if ($link.length === 0)
23167						{
23168							var a = $('<a href="' + link + '" id="redactor-img-tmp">' + this.utils.getOuterHtml($image) + '</a>');
23169							if (target)
23170							{
23171								a.attr('target', '_blank');
23172							}
23173
23174							$image = $image.replaceWith(a);
23175							$link = this.core.editor().find('#redactor-img-tmp');
23176							$link.removeAttr('id');
23177						}
23178						else
23179						{
23180							$link.attr('href', link);
23181							if (target)
23182							{
23183								$link.attr('target', '_blank');
23184							}
23185							else
23186							{
23187								$link.removeAttr('target');
23188							}
23189						}
23190					}
23191					else if ($link.length !== 0)
23192					{
23193						$link.replaceWith(this.utils.getOuterHtml($image));
23194					}
23195
23196                    this.image.addCaption($image, $link);
23197					this.modal.close();
23198
23199					// buffer
23200					this.buffer.set();
23201
23202				},
23203				setFloating: function($image)
23204				{
23205    				var $figure = $image.closest('figure', this.$editor[0]);
23206    				var $container = ($figure.length !== 0) ? $figure : $image;
23207					var floating = $('#redactor-image-align').val();
23208
23209					var imageFloat = '';
23210					var imageDisplay = '';
23211					var imageMargin = '';
23212					var textAlign = '';
23213
23214					switch (floating)
23215					{
23216						case 'left':
23217							imageFloat = 'left';
23218							imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
23219						break;
23220						case 'right':
23221							imageFloat = 'right';
23222							imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
23223						break;
23224						case 'center':
23225
23226                            if ($figure.length !== 0)
23227                            {
23228                                textAlign = 'center';
23229                            }
23230                            else
23231                            {
23232	    						imageDisplay = 'block';
23233    							imageMargin = 'auto';
23234							}
23235
23236						break;
23237					}
23238
23239					$container.css({ 'float': imageFloat, 'display': imageDisplay, 'margin': imageMargin, 'text-align': textAlign });
23240					$container.attr('rel', $image.attr('style'));
23241				},
23242				addCaption: function($image, $link)
23243				{
23244                    var caption = $('#redactor-image-caption').val();
23245
23246                    var $target = ($link.length !== 0) ? $link : $image;
23247					var $figcaption = $target.next();
23248
23249					if ($figcaption.length === 0 || $figcaption[0].tagName !== 'FIGCAPTION')
23250					{
23251						$figcaption = false;
23252					}
23253
23254					if (caption !== '')
23255					{
23256						if ($figcaption === false)
23257						{
23258							$figcaption = $('<figcaption />').text(caption);
23259							$target.after($figcaption);
23260						}
23261						else
23262						{
23263							$figcaption.text(caption);
23264						}
23265					}
23266					else if ($figcaption !== false)
23267					{
23268						$figcaption.remove();
23269					}
23270				},
23271				remove: function(e, $image, index)
23272				{
23273					$image = (typeof $image === 'undefined') ? $(this.observe.image) : $image;
23274
23275					// delete from modal
23276					if (typeof e !== 'boolean')
23277					{
23278						this.buffer.set();
23279					}
23280
23281					this.events.stopDetectChanges();
23282
23283					var $link = $image.closest('a', this.core.editor()[0]);
23284					var $figure = $image.closest(this.opts.imageTag, this.core.editor()[0]);
23285					var $parent = $image.parent();
23286
23287					// callback
23288                    var imageDeleteStop = this.core.callback('imageDelete', e, $image[0]);
23289					if (imageDeleteStop === false)
23290					{
23291						if (e) e.preventDefault();
23292						return false;
23293					}
23294
23295					if ($('#redactor-image-box').length !== 0)
23296					{
23297						$parent = $('#redactor-image-box').parent();
23298					}
23299
23300					var $next, $prev;
23301					if ($figure.length !== 0)
23302					{
23303						$prev = $figure.prev();
23304						$next = $figure.next();
23305						$figure.remove();
23306					}
23307					else if ($link.length !== 0)
23308					{
23309						$parent = $link.parent();
23310						$link.remove();
23311					}
23312					else
23313					{
23314						$image.remove();
23315					}
23316
23317					$('#redactor-image-box').remove();
23318
23319					if (e !== false)
23320					{
23321						if ($next && $next.length !== 0)
23322						{
23323							this.caret.start($next);
23324						}
23325						else if ($prev && $prev.length !== 0)
23326						{
23327                            this.caret.end($prev);
23328						}
23329					}
23330
23331					if (typeof e !== 'boolean')
23332					{
23333						this.modal.close();
23334					}
23335
23336					this.utils.restoreScroll();
23337					this.observe.image = false;
23338					this.events.startDetectChanges();
23339					this.placeholder.enable();
23340					this.code.sync();
23341
23342				}
23343			};
23344		},
23345
23346		// =indent
23347		indent: function()
23348		{
23349			return {
23350				increase: function()
23351				{
23352					if (!this.list.get())
23353					{
23354						return;
23355					}
23356
23357					var $current = $(this.selection.current()).closest('li');
23358					var $list = $current.closest('ul, ol', this.core.editor()[0]);
23359
23360					var $li = $current.closest('li');
23361					var $prev = $li.prev();
23362					if ($prev.length === 0 || $prev[0].tagName !== 'LI')
23363					{
23364						return;
23365					}
23366
23367					this.buffer.set();
23368
23369
23370					if (this.utils.isCollapsed())
23371					{
23372						var listTag = $list[0].tagName;
23373						var $newList = $('<' + listTag + ' />');
23374
23375						this.selection.save();
23376
23377                        var $ol = $prev.find('ol').first();
23378                        if ($ol.length === 1)
23379                        {
23380                            $ol.append($current);
23381                        }
23382                        else
23383                        {
23384                            var listTag = $list[0].tagName;
23385                            var $newList = $('<' + listTag + ' />');
23386                            $newList.append($current);
23387                            $prev.append($newList);
23388                        }
23389
23390						this.selection.restore();
23391					}
23392					else
23393					{
23394						document.execCommand('indent');
23395
23396						// normalize
23397						this.selection.save();
23398						this.indent.removeEmpty();
23399						this.indent.normalize();
23400						this.selection.restore();
23401					}
23402				},
23403				decrease: function()
23404				{
23405					if (!this.list.get())
23406					{
23407						return;
23408					}
23409
23410					var $current = $(this.selection.current()).closest('li');
23411					var $list = $current.closest('ul, ol', this.core.editor()[0]);
23412
23413					this.buffer.set();
23414
23415					document.execCommand('outdent');
23416
23417					var $item = $(this.selection.current()).closest('li', this.core.editor()[0]);
23418
23419					if (this.utils.isCollapsed())
23420					{
23421						this.indent.repositionItem($item);
23422					}
23423
23424					if ($item.length === 0)
23425					{
23426						document.execCommand('formatblock', false, 'p');
23427						$item = $(this.selection.current());
23428						var $next = $item.next();
23429						if ($next.length !== 0 && $next[0].tagName === 'BR')
23430						{
23431							$next.remove();
23432						}
23433					}
23434
23435					// normalize
23436					this.selection.save();
23437					this.indent.removeEmpty();
23438					this.indent.normalize();
23439					this.selection.restore();
23440
23441				},
23442				repositionItem: function($item)
23443				{
23444    				var $next = $item.next();
23445    				if ($next.length !== 0 && ($next[0].tagName !== 'UL' || $next[0].tagName !== 'OL'))
23446    				{
23447        				$item.append($next);
23448    				}
23449
23450					var $prev = $item.prev();
23451					if ($prev.length !== 0 && $prev[0].tagName !== 'LI')
23452					{
23453						this.selection.save();
23454						var $li = $item.parents('li', this.core.editor()[0]);
23455						$li.after($item);
23456						this.selection.restore();
23457					}
23458				},
23459				normalize: function()
23460				{
23461					this.core.editor().find('li').each($.proxy(function(i,s)
23462					{
23463						var $el = $(s);
23464
23465						// remove style
23466                        var filter = '';
23467                        if (this.opts.keepStyleAttr.length !== 0)
23468                        {
23469                            filter = ',' + this.opts.keepStyleAttr.join(',');
23470                        }
23471
23472						$el.find(this.opts.inlineTags.join(',')).not('img' + filter).removeAttr('style');
23473
23474						var $parent = $el.parent();
23475						if ($parent.length !== 0 && $parent[0].tagName === 'LI')
23476						{
23477							$parent.after($el);
23478							return;
23479						}
23480
23481						var $next = $el.next();
23482						if ($next.length !== 0 && ($next[0].tagName === 'UL' || $next[0].tagName === 'OL'))
23483						{
23484							$el.append($next);
23485						}
23486
23487					}, this));
23488
23489				},
23490				removeEmpty: function($list)
23491				{
23492					var $lists = this.core.editor().find('ul, ol');
23493					var $items = this.core.editor().find('li');
23494
23495					$items.each($.proxy(function(i, s)
23496					{
23497						this.indent.removeItemEmpty(s);
23498
23499					}, this));
23500
23501					$lists.each($.proxy(function(i, s)
23502					{
23503						this.indent.removeItemEmpty(s);
23504
23505					}, this));
23506
23507					$items.each($.proxy(function(i, s)
23508					{
23509						this.indent.removeItemEmpty(s);
23510
23511					}, this));
23512				},
23513				removeItemEmpty: function(s)
23514				{
23515					var html = s.innerHTML.replace(/[\t\s\n]/g, '');
23516					html = html.replace(/<span><\/span>/g, '');
23517
23518					if (html === '')
23519					{
23520						$(s).remove();
23521					}
23522				}
23523			};
23524		},
23525
23526		// =inline
23527		inline: function()
23528		{
23529			return {
23530                format: function(tag, attr, value, type)
23531				{
23532    				// Stop formatting pre/code
23533					if (this.utils.isCurrentOrParent(['PRE', 'CODE'])) return;
23534
23535					// Get params
23536					var params = this.inline.getParams(attr, value, type);
23537
23538                    // Arrange tag
23539                    tag = this.inline.arrangeTag(tag);
23540
23541                    this.placeholder.hide();
23542					this.buffer.set();
23543
23544					(this.utils.isCollapsed()) ? this.inline.formatCollapsed(tag, params) : this.inline.formatUncollapsed(tag, params);
23545				},
23546				formatCollapsed: function(tag, params)
23547				{
23548    				var newInline;
23549    				var inline = this.selection.inline();
23550
23551    				if (inline)
23552					{
23553                        var currentTag = inline.tagName.toLowerCase();
23554                        if (currentTag === tag)
23555						{
23556							// empty = remove
23557							if (this.utils.isEmpty(inline.innerHTML))
23558							{
23559								this.caret.after(inline);
23560								$(inline).remove();
23561							}
23562							// not empty = break
23563							else
23564							{
23565								var $first = this.inline.insertBreakpoint(inline, currentTag);
23566								this.caret.after($first);
23567							}
23568						}
23569                        else if ($(inline).closest(tag).length === 0)
23570                        {
23571							newInline = this.inline.insertInline(tag);
23572							newInline = this.inline.setParams(newInline, params);
23573						}
23574						else
23575						{
23576    						var $first = this.inline.insertBreakpoint(inline, currentTag);
23577                            this.caret.after($first);
23578						}
23579    				}
23580    				else
23581    				{
23582                        newInline = this.inline.insertInline(tag);
23583                        newInline = this.inline.setParams(newInline, params);
23584    				}
23585				},
23586				formatUncollapsed: function(tag, params)
23587				{
23588    				this.selection.save();
23589
23590                    var nodes = this.inline.getClearedNodes();
23591                    this.inline.setNodesStriked(nodes, tag, params);
23592
23593                    this.selection.restore();
23594
23595                    document.execCommand('strikethrough');
23596
23597                    this.selection.saveInstant();
23598
23599                    var self = this;
23600                    this.core.editor().find('strike').each(function()
23601    				{
23602                        var $el = self.utils.replaceToTag(this, tag);
23603        				self.inline.setParams($el[0], params);
23604
23605        				var $inside = $el.find(tag);
23606        				var $parent = $el.parent();
23607        				var $parentAround = $parent.parent();
23608
23609                        // revert formatting (safari bug)
23610                        if ($parentAround.length !== 0 && $parentAround[0].tagName.toLowerCase() === tag && $parentAround.html() == $parent[0].outerHTML)
23611                        {
23612                            $el.replaceWith(function() { return $(this).contents(); });
23613                            $parentAround.replaceWith(function() { return $(this).contents(); });
23614
23615                            return;
23616                        }
23617
23618        				// remove inside
23619        				if ($inside.length !== 0) self.inline.cleanInsideOrParent($inside, params);
23620
23621                        // same parent
23622                        if ($parent.html() == $el[0].outerHTML) self.inline.cleanInsideOrParent($parent, params);
23623
23624                        // bugfix: remove empty inline tags after selection
23625                        if (self.detect.isFirefox())
23626                        {
23627                            self.core.editor().find(tag + ':empty').remove();
23628                        }
23629    				});
23630
23631    				this.selection.restoreInstant();
23632				},
23633				cleanInsideOrParent: function($el, params)
23634				{
23635                    if (params)
23636                    {
23637                        for (var key in params.data)
23638                        {
23639        				    this.inline.removeSpecificAttr($el, key, params.data[key]);
23640        				}
23641    				}
23642				},
23643    			getClearedNodes: function()
23644    			{
23645                    var nodes = this.selection.nodes();
23646        			var newNodes = [];
23647        			var len = nodes.length;
23648                    var started = 0;
23649
23650                    // find array slice
23651                    for (var i = 0; i < len; i++)
23652					{
23653						if ($(nodes[i]).hasClass('redactor-selection-marker'))
23654						{
23655    						started = i + 2;
23656    						break;
23657						}
23658					}
23659
23660                    // find selected inline & text nodes
23661                    for (var i = 0; i < len; i++)
23662					{
23663						if (i >= started && !this.utils.isBlockTag(nodes[i].tagName))
23664						{
23665                            newNodes.push(nodes[i]);
23666						}
23667					}
23668
23669					return newNodes;
23670    			},
23671                isConvertableAttr: function(node, name, value)
23672                {
23673                    var nodeAttrValue = $(node).attr(name);
23674                    if (nodeAttrValue)
23675                    {
23676                        if (name === 'style')
23677                        {
23678                            value = $.trim(value).replace(/;$/, '')
23679
23680                            var rules = value.split(';');
23681                            var count = 0;
23682                            for (var i = 0; i < rules.length; i++)
23683                            {
23684                                var arr = rules[i].split(':');
23685                                var ruleName = $.trim(arr[0]);
23686                                var ruleValue = $.trim(arr[1]);
23687
23688                                if (ruleName.search(/color/) !== -1)
23689                                {
23690                                    var val = $(node).css(ruleName);
23691                                    if (val && (val === ruleValue || this.utils.rgb2hex(val) === ruleValue))
23692                                    {
23693                                        count++;
23694                                    }
23695                                }
23696                                else if ($(node).css(ruleName) === ruleValue)
23697                                {
23698                                    count++;
23699                                }
23700                            }
23701
23702                            if (count === rules.length)
23703                            {
23704                                return 1;
23705                            }
23706                        }
23707                        else if (nodeAttrValue === value)
23708                        {
23709                            return 1;
23710                        }
23711                    }
23712
23713                    return 0;
23714
23715                },
23716    			isConvertable: function(node, nodeTag, tag, params)
23717    			{
23718                    if (nodeTag === tag)
23719                    {
23720                        if (params)
23721                        {
23722                            var count = 0;
23723                            for (var key in params.data)
23724                            {
23725                                count += this.inline.isConvertableAttr(node, key, params.data[key]);
23726                            }
23727
23728                            if (count === Object.keys(params.data).length)
23729                            {
23730                                return true;
23731                            }
23732                        }
23733                        else
23734                        {
23735                            return true;
23736                        }
23737                    }
23738
23739                    return false;
23740    			},
23741    			setNodesStriked: function(nodes, tag, params)
23742    			{
23743                    for (var i = 0; i < nodes.length; i++)
23744					{
23745                        var nodeTag = (nodes[i].tagName) ? nodes[i].tagName.toLowerCase() : undefined;
23746
23747                        var parent = nodes[i].parentNode;
23748                        var parentTag = (parent && parent.tagName) ? parent.tagName.toLowerCase() : undefined;
23749
23750                        var convertable = this.inline.isConvertable(parent, parentTag, tag, params);
23751                        if (convertable)
23752                        {
23753                            var $el = $(parent).replaceWith(function()
23754                            {
23755                                return $('<strike>').append($(this).contents());
23756                            });
23757
23758                            $el.attr('data-redactor-inline-converted');
23759                        }
23760
23761                        var convertable = this.inline.isConvertable(nodes[i], nodeTag, tag, params);
23762                        if (convertable)
23763                        {
23764                            var $el = $(nodes[i]).replaceWith(function()
23765                            {
23766                                return $('<strike>').append($(this).contents());
23767                            });
23768                        }
23769    				}
23770    			},
23771                insertBreakpoint: function(inline, currentTag)
23772				{
23773					var breakpoint = document.createElement('span');
23774					breakpoint.id = 'redactor-inline-breakpoint';
23775					breakpoint = this.insert.node(breakpoint);
23776
23777					var end = this.utils.isEndOfElement(inline);
23778					var code = this.utils.getOuterHtml(inline);
23779					var endTag = (end) ? '' : '<' + currentTag + '>';
23780
23781                    code = code.replace(/<span id="redactor-inline-breakpoint"><\/span>/i, "</" + currentTag + ">" + endTag);
23782
23783					var $code = $(code);
23784					$(inline).replaceWith($code);
23785
23786					if (endTag !== '')
23787					{
23788    					this.utils.cloneAttributes(inline, $code.last());
23789					}
23790
23791					return $code.first();
23792				},
23793                insertInline: function(tag)
23794				{
23795					var node = document.createElement(tag);
23796
23797					this.insert.node(node);
23798					this.caret.start(node);
23799
23800					return node;
23801				},
23802				arrangeTag: function(tag)
23803				{
23804                    var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
23805					var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
23806
23807    				tag = tag.toLowerCase();
23808
23809					for (var i = 0; i < tags.length; i++)
23810					{
23811						if (tag === tags[i])
23812						{
23813							tag = replaced[i];
23814						}
23815					}
23816
23817					return tag;
23818				},
23819				getStyleParams: function(params)
23820				{
23821                    var result = {};
23822    				var rules = params.trim().replace(/;$/, '').split(';');
23823    				for (var i = 0; i < rules.length; i++)
23824    				{
23825        				var rule = rules[i].split(':');
23826        				if (rule)
23827        				{
23828                            result[rule[0].trim()] = rule[1].trim();
23829                        }
23830    				}
23831
23832    				return result;
23833				},
23834				getParams: function(attr, value, type)
23835				{
23836 				    var data = false;
23837    				var func = 'toggle';
23838    				if (typeof attr === 'object')
23839    				{
23840        				data = attr;
23841        				func = (value !== undefined) ? value : func;
23842    				}
23843    				else if (attr !== undefined && value !== undefined)
23844    				{
23845        				data = {};
23846        				data[attr] = value;
23847        				func = (type !== undefined) ? type : func;
23848    				}
23849
23850    				return (data) ? { 'func': func, 'data': data } : false;
23851				},
23852				setParams: function(node, params)
23853				{
23854                    if (params)
23855                    {
23856                        for (var key in params.data)
23857                        {
23858                            var $node = $(node);
23859                            if (key === 'style')
23860                            {
23861                                node = this.inline[params.func + 'Style'](params.data[key], node);
23862                                $node.attr('data-redactor-style-cache', $node.attr('style'));
23863                            }
23864                            else if (key === 'class')
23865                            {
23866                                node = this.inline[params.func + 'Class'](params.data[key], node);
23867                            }
23868                            // attr
23869                            else
23870                            {
23871                                node = (params.func === 'remove') ? this.inline[params.func + 'Attr'](key, node) : this.inline[params.func + 'Attr'](key, params.data[key], node);
23872                            }
23873
23874                            if (key === 'style' && node.tagName === 'SPAN')
23875                            {
23876                                $node.attr('data-redactor-span', true);
23877                            }
23878                        }
23879                    }
23880
23881                    return node;
23882				},
23883
23884                // Each
23885                eachInline: function(node, callback)
23886                {
23887                    var lastNode;
23888                    var nodes = (node === undefined) ? this.selection.inlines() : [
23888node];
23889    				if (nodes)
23890    				{
23891        				for (var i = 0; i < nodes.length; i++)
23892        				{
23893            				lastNode = callback(nodes[i])[0];
23894        				}
23895    				}
23896
23897    				return lastNode;
23898                },
23899
23900                // Class
23901                replaceClass: function(value, node)
23902				{
23903    				return this.inline.eachInline(node, function(el)
23904    				{
23905                        return $(el).removeAttr('class').addClass(value);
23906    				});
23907				},
23908				toggleClass: function(value, node)
23909				{
23910    				return this.inline.eachInline(node, function(el)
23911    				{
23912                        return $(el).toggleClass(value);
23913    				});
23914				},
23915				addClass: function(value, node)
23916				{
23917    				return this.inline.eachInline(node, function(el)
23918    				{
23919                        return $(el).addClass(value);
23920    				});
23921				},
23922				removeClass: function(value, node)
23923				{
23924    				return this.inline.eachInline(node, function(el)
23925    				{
23926                        return $(el).removeClass(value);
23927    				});
23928				},
23929				removeAllClass: function(node)
23930				{
23931    				return this.inline.eachInline(node, function(el)
23932    				{
23933                        return $(el).removeAttr('class');
23934    				});
23935				},
23936
23937				// Attr
23938				replaceAttr: function(name, value, node)
23939				{
23940    				return this.inline.eachInline(node, function(el)
23941    				{
23942                        return $(el).removeAttr(name).attr(name. value);
23943    				});
23944				},
23945				toggleAttr: function(name, value, node)
23946				{
23947    				return this.inline.eachInline(node, function(el)
23948    				{
23949        				var attr = $(el).attr(name);
23950
23951        				return (attr) ? $(el).removeAttr(name) : $(el).attr(name. value);
23952    				});
23953				},
23954				addAttr: function(name, value, node)
23955				{
23956    				return this.inline.eachInline(node, function(el)
23957    				{
23958                        return $(el).attr(name, value);
23959    				});
23960				},
23961				removeAttr: function(name, node)
23962				{
23963    				return this.inline.eachInline(node, function(el)
23964    				{
23965        				var $el = $(el);
23966
23967                        $el.removeAttr(name);
23968                        if (name === 'style')
23969                        {
23970        				    $el.removeAttr('data-redactor-style-cache');
23971        				}
23972
23973                        return $el;
23974    				});
23975				},
23976				removeAllAttr: function(node)
23977				{
23978    				return this.inline.eachInline(node, function(el)
23979    				{
23980        				var $el = $(el);
23981                        var len = el.attributes.length;
23982						for (var z = 0; z < len; z++)
23983						{
23984							$el.removeAttr(el.attributes[z].name);
23985						}
23986
23987                        return $el;
23988    				});
23989				},
23990				removeSpecificAttr: function(node, key, value)
23991				{
23992    				var $el = $(node);
23993    				if (key === 'style')
23994    				{
23995        				var arr = value.split(':');
23996        				var name = arr[0].trim();
23997                        $el.css(name, '');
23998
23999                        if (this.utils.removeEmptyAttr(node, 'style'))
24000                        {
24001                            $el.removeAttr('data-redactor-style-cache');
24002                        }
24003    				}
24004    				else
24005    				{
24006        				$el.removeAttr(key)[0];
24007    				}
24008				},
24009
24010				// Style
24011				hasParentStyle: function($el)
24012				{
24013                    var $parent = $el.parent();
24014
24015                    return ($parent.length === 1 && $parent[0].tagName === $el[0].tagName && $parent.html() === $el[0].outerHTML) ? $parent : false;
24016				},
24017				addParentStyle: function($el)
24018				{
24019                    var $parent = this.inline.hasParentStyle($el);
24020                    if ($parent)
24021                    {
24022                        var style = this.inline.getStyleParams($el.attr('style'));
24023                        $parent.css(style);
24024                        $parent.attr('data-redactor-style-cache', $parent.attr('style'));
24025
24026                        $el.replaceWith(function()
24027                        {
24028                            return $(this).contents();
24029                        });
24030                    }
24031                    else
24032                    {
24033                        $el.attr('data-redactor-style-cache', $el.attr('style'));
24034                    }
24035
24036                    return $el;
24037				},
24038				replaceStyle: function(params, node)
24039				{
24040    				params = this.inline.getStyleParams(params);
24041
24042                    var self = this;
24043    				return this.inline.eachInline(node, function(el)
24044    				{
24045        				var $el = $(el);
24046                        $el.removeAttr('style').css(params);
24047
24048                        var style = $el.attr('style');
24049                        if (style) $el.attr('style', style.replace(/"/g, '\''));
24050
24051                        $el = self.inline.addParentStyle($el);
24052
24053                        return $el;
24054    				});
24055				},
24056				toggleStyle: function(params, node)
24057				{
24058    				params = this.inline.getStyleParams(params);
24059
24060                    var self = this;
24061    				return this.inline.eachInline(node, function(el)
24062    				{
24063        				var $el = $(el);
24064
24065                        for (var key in params)
24066                        {
24067                            var newVal = params[key];
24068                            var oldVal = $el.css(key);
24069
24070                            oldVal = (self.utils.isRgb(oldVal)) ? self.utils.rgb2hex(oldVal) : oldVal.replace(/"/g, '');
24071                            newVal = (self.utils.isRgb(newVal)) ? self.utils.rgb2hex(newVal) : newVal.replace(/"/g, '');
24072
24073                            if (oldVal === newVal)
24074                            {
24075                                $el.css(key, '');
24076                            }
24077                            else
24078                            {
24079                                $el.css(key, newVal);
24080                            }
24081                        }
24082
24083                        var style = $el.attr('style');
24084                        if (style) $el.attr('style', style.replace(/"/g, '\''));
24085
24086                        if (!self.utils.removeEmptyAttr(el, 'style'))
24087                        {
24088                            $el = self.inline.addParentStyle($el);
24089                        }
24090                        else
24091                        {
24092                            $el.removeAttr('data-redactor-style-cache');
24093                        }
24094
24095
24096                        return $el;
24097    				});
24098				},
24099				addStyle: function(params, node)
24100				{
24101                    params = this.inline.getStyleParams(params);
24102
24103                    var self = this;
24104    				return this.inline.eachInline(node, function(el)
24105    				{
24106                        var $el = $(el);
24107
24108                        $el.css(params);
24109
24110                        var style = $el.attr('style');
24111                        if (style) $el.attr('style', style.replace(/"/g, '\''));
24112
24113                        $el = self.inline.addParentStyle($el);
24114
24115                        return $el;
24116    				});
24117				},
24118				removeStyle: function(params, node)
24119				{
24120    				params = this.inline.getStyleParams(params);
24121
24122                    var self = this;
24123    				return this.inline.eachInline(node, function(el)
24124    				{
24125        				var $el = $(el);
24126
24127                        for (var key in params)
24128                        {
24129                            $el.css(key, '');
24130                        }
24131
24132                        if (self.utils.removeEmptyAttr(el, 'style'))
24133                        {
24134                            $el.removeAttr('data-redactor-style-cache');
24135                        }
24136                        else
24137                        {
24138                            $el.attr('data-redactor-style-cache', $el.attr('style'));
24139                        }
24140
24141                        return $el;
24142    				});
24143				},
24144				removeAllStyle: function(node)
24145				{
24146    				return this.inline.eachInline(node, function(el)
24147    				{
24148                        return $(el).removeAttr('style').removeAttr('data-redactor-style-cache');
24149    				});
24150				},
24151                removeStyleRule: function(name)
24152				{
24153					var parent = this.selection.parent();
24154					var nodes = this.selection.inlines();
24155
24156                    this.buffer.set();
24157
24158					if (parent && parent.tagName === 'SPAN')
24159					{
24160						this.inline.removeStyleRuleAttr($(parent), name);
24161					}
24162
24163                    for (var i = 0; i < nodes.length; i++)
24164					{
24165    					var el = nodes[i];
24166						var $el = $(el);
24167						if ($.inArray(el.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$el.hasClass('redactor-selection-marker'))
24168						{
24169                            this.inline.removeStyleRuleAttr($el, name);
24170						}
24171					}
24172
24173				},
24174				removeStyleRuleAttr: function($el, name)
24175				{
24176                    $el.css(name, '');
24177					if (this.utils.removeEmptyAttr($el, 'style'))
24178					{
24179						$el.removeAttr('data-redactor-style-cache');
24180					}
24181					else
24182					{
24183    					$el.attr('data-redactor-style-cache', $el.attr('style'));
24184					}
24185				},
24186
24187				// Update
24188				update: function(tag, attr, value, type)
24189				{
24190                    tag = this.inline.arrangeTag(tag);
24191
24192					var params = this.inline.getParams(attr, value, type);
24193					var nodes = this.selection.inlines();
24194					var result = [];
24195
24196                    if (nodes)
24197                    {
24198                        for (var i = 0; i < nodes.length; i++)
24199                        {
24200                            var el = nodes[i];
24201                            if (tag === '*' || el.tagName.toLowerCase() === tag)
24202                            {
24203    						    result.push(this.inline.setParams(el, params));
24204    						}
24205                        }
24206                    }
24207
24208                    return result;
24209				},
24210
24211                // All
24212				removeFormat: function()
24213				{
24214    				this.selection.save();
24215
24216    				var nodes = this.inline.getClearedNodes();
24217    				for (var i = 0; i < nodes.length; i++)
24218    				{
24219        				if (nodes[i].nodeType === 1)
24220        				{
24221            				$(nodes[i]).replaceWith(function()
24222                            {
24223                                return $(this).contents();
24224                            });
24225        				}
24226    				}
24227
24228    				this.selection.restore();
24229				}
24230
24231			};
24232		},
24233
24234		// =insert
24235		insert: function()
24236		{
24237			return {
24238				set: function(html)
24239				{
24240					this.placeholder.hide();
24241
24242					this.code.set(html);
24243					this.focus.end();
24244
24245					this.placeholder.enable();
24246				},
24247				html: function(html, data)
24248				{
24249					this.placeholder.hide();
24250					this.core.editor().focus();
24251
24252					var block = this.selection.block();
24253					var inline = this.selection.inline();
24254
24255					// clean
24256					if (typeof data === 'undefined')
24257					{
24258						data = this.clean.getCurrentType(html, true);
24259						html = this.clean.onPaste(html, data, true);
24260					}
24261
24262					html = $.parseHTML(html);
24263
24264                    // end node
24265                    var endNode = $(html).last();
24266
24267					// delete selected content
24268					var sel = this.selection.get();
24269					var range = this.selection.range(sel);
24270					range.deleteContents();
24271
24272					this.selection.update(sel, range);
24273
24274					// insert list in list
24275					if (data.lists)
24276					{
24277						var $list = $(html);
24278						if ($list.length !== 0 && ($list[0].tagName === 'UL' || $list[0].tagName === 'OL'))
24279						{
24280
24281							this.insert.appendLists(block, $list);
24282							return;
24283						}
24284					}
24285
24286					if (data.blocks && block)
24287					{
24288						if (this.utils.isSelectAll())
24289						{
24290							this.core.editor().html(html);
24291							this.focus.end();
24292						}
24293						else
24294						{
24295							var breaked = this.utils.breakBlockTag();
24296							if (breaked === false)
24297							{
24298								this.insert.placeHtml(html);
24299							}
24300							else
24301							{
24302    							var $last = $(html).children().last();
24303								$last.append(this.marker.get());
24304
24305								if (breaked.type === 'start')
24306								{
24307									breaked.$block.before(html);
24308								}
24309								else
24310								{
24311									breaked.$block.after(html);
24312								}
24313
24314								this.selection.restore();
24315								this.core.editor().find('p').each(function()
24316								{
24317									if ($.trim(this.innerHTML) === '')
24318									{
24319										$(this).remove();
24320									}
24321								});
24322							}
24323						}
24324					}
24325					else
24326					{
24327						if (inline)
24328						{
24329							// remove same tag inside
24330							var $div = $("<div/>").html(html);
24331							$div.find(inline.tagName.toLowerCase()).each(function()
24332							{
24333								$(this).contents().unwrap();
24334							});
24335
24336							html = $div.html();
24337							html = $.parseHTML(html);
24338
24339							endNode = $(html).last();
24340
24341
24342						}
24343
24344						if (this.utils.isSelectAll())
24345						{
24346							var $node = $(this.opts.emptyHtml);
24347							this.core.editor().html('').append($node);
24348							$node.html(html);
24349							this.caret.end($node);
24350						}
24351						else
24352						{
24353							this.insert.placeHtml(html);
24354						}
24355					}
24356
24357					this.utils.disableSelectAll();
24358
24359					if (data.pre) this.clean.cleanPre();
24360
24361                    this.caret.end(endNode);
24362                    this.linkify.format();
24363				},
24364				text: function(text)
24365				{
24366					text = text.toString();
24367					text = $.trim(text);
24368
24369					var tmp = document.createElement('div');
24370					tmp.innerHTML = text;
24371					text = tmp.textContent || tmp.innerText;
24372
24373					if (typeof text === 'undefined')
24374					{
24375						return;
24376					}
24377
24378					this.placeholder.hide();
24379					this.core.editor().focus();
24380
24381					// blocks
24382					var blocks = this.selection.blocks();
24383
24384					// nl to spaces
24385					text = text.replace(/\n/g, ' ');
24386
24387					// select all
24388					if (this.utils.isSelectAll())
24389					{
24390						var $node = $(this.opts.emptyHtml);
24391						this.core.editor().html('').append($node);
24392						$node.html(text);
24393						this.caret.end($node);
24394					}
24395					else
24396					{
24397						// insert
24398						var sel = this.selection.get();
24399						var node = document.createTextNode(text);
24400
24401						if (sel.getRangeAt && sel.rangeCount)
24402						{
24403							var range = sel.getRangeAt(0);
24404							range.deleteContents();
24405							range.insertNode(node);
24406							range.setStartAfter(node);
24407							range.collapse(true);
24408
24409							this.selection.update(sel, range);
24410						}
24411
24412						// wrap node if selected two or more block tags
24413						if (blocks.length > 1)
24414						{
24415							$(node).wrap('<p>');
24416							this.caret.after(node);
24417						}
24418					}
24419
24420					this.utils.disableSelectAll();
24421					this.linkify.format();
24422					this.clean.normalizeCurrentHeading();
24423
24424				},
24425				raw: function(html)
24426				{
24427					this.placeholder.hide();
24428					this.core.editor().focus();
24429
24430					var sel = this.selection.get();
24431
24432					var range = this.selection.range(sel);
24433					range.deleteContents();
24434
24435		            var el = document.createElement("div");
24436		            el.innerHTML = html;
24437
24438		            var frag = document.createDocumentFragment(), node, lastNode;
24439		            while ((node = el.firstChild))
24440		            {
24441		                lastNode = frag.appendChild(node);
24442		            }
24443
24444		            range.insertNode(frag);
24445
24446					if (lastNode)
24447					{
24448						range = range.cloneRange();
24449						range.setStartAfter(lastNode);
24450						range.collapse(true);
24451						sel.removeAllRanges();
24452						sel.addRange(range);
24453					}
24454				},
24455				node: function(node, deleteContent)
24456				{
24457					this.placeholder.hide();
24458
24459					if (typeof this.start !== 'undefined')
24460					{
24461						this.core.editor().focus();
24462					}
24463
24464					node = node[0] || node;
24465
24466					var block = this.selection.block();
24467					var gap = this.utils.isBlockTag(node.tagName);
24468					var result = true;
24469
24470					if (this.utils.isSelectAll())
24471					{
24472						if (gap)
24473						{
24474							this.core.editor().html(node);
24475						}
24476						else
24477						{
24478							this.core.editor().html($('<p>').html(node));
24479						}
24480
24481						this.code.sync();
24482					}
24483					else if (gap && block)
24484					{
24485						var breaked = this.utils.breakBlockTag();
24486						if (breaked === false)
24487						{
24488							this.insert.placeNode(node, deleteContent);
24489						}
24490						else
24491						{
24492							if (breaked.type === 'start')
24493							{
24494								breaked.$block.before(node);
24495							}
24496							else
24497							{
24498								breaked.$block.after(node);
24499							}
24500
24501							this.core.editor().find('p:empty').remove();
24502						}
24503					}
24504					else
24505					{
24506						result = this.insert.placeNode(node, deleteContent);
24507					}
24508
24509					this.utils.disableSelectAll();
24510
24511					if (result)
24512					{
24513					    this.caret.end(node);
24514					}
24515
24516					return node;
24517
24518				},
24519				appendLists: function(block, $list)
24520				{
24521					var $block = $(block);
24522					var last;
24523					var isEmpty = this.utils.isEmpty(block.innerHTML);
24524
24525					if (isEmpty || this.utils.isEndOfElement(block))
24526					{
24527						last = $block;
24528						$list.find('li').each(function()
24529						{
24530							last.after(this);
24531							last = $(this);
24532						});
24533
24534						if (isEmpty)
24535						{
24536							$block.remove();
24537						}
24538					}
24539					else if (this.utils.isStartOfElement(block))
24540					{
24541						$list.find('li').each(function()
24542						{
24543							$block.before(this);
24544							last = $(this);
24545						});
24546					}
24547					else
24548					{
24549				        var endOfNode = this.selection.extractEndOfNode(block);
24550
24551				        $block.after($('<li>').append(endOfNode));
24552				        $block.append($list);
24553						last = $list;
24554					}
24555
24556					this.marker.remove();
24557
24558					if (last)
24559					{
24560						this.caret.end(last);
24561					}
24562
24563					this.linkify.format();
24564				},
24565				placeHtml: function(html)
24566				{
24567					var marker = document.createElement('span');
24568					marker.id = 'redactor-insert-marker';
24569					marker = this.insert.node(marker);
24570
24571					$(marker).before(html);
24572					this.selection.restore();
24573					this.caret.after(marker);
24574					$(marker).remove();
24575				},
24576				placeNode: function(node, deleteContent)
24577				{
24578					var sel = this.selection.get();
24579					var range = this.selection.range(sel);
24580					if (range == null)
24581					{
24582    					return false;
24583					}
24584
24585					if (deleteContent !== false)
24586					{
24587						range.deleteContents();
24588					}
24589
24590					range.insertNode(node);
24591					range.collapse(false);
24592
24593					this.selection.update(sel, range);
24594				},
24595				nodeToPoint: function(e, node)
24596				{
24597					this.placeholder.hide();
24598
24599					node = node[0] || node;
24600
24601					if (this.utils.isEmpty())
24602					{
24603						node = (this.utils.isBlock(node)) ? node : $('<p />').append(node);
24604
24605						this.core.editor().html(node);
24606
24607						return node;
24608					}
24609
24610					var range;
24611					var x = e.clientX, y = e.clientY;
24612					if (document.caretPositionFromPoint)
24613					{
24614					    var pos = document.caretPositionFromPoint(x, y);
24615					    var sel = document.getSelection();
24616					    range = sel.getRangeAt(0);
24617					    range.setStart(pos.offsetNode, pos.offset);
24618					    range.collapse(true);
24619					    range.insertNode(node);
24620					}
24621					else if (document.caretRangeFromPoint)
24622					{
24623					    range = document.caretRangeFromPoint(x, y);
24624					    range.insertNode(node);
24625					}
24626					else if (typeof document.body.createTextRange !== "undefined")
24627					{
24628				        range = document.body.createTextRange();
24629				        range.moveToPoint(x, y);
24630				        var endRange = range.duplicate();
24631				        endRange.moveToPoint(x, y);
24632				        range.setEndPoint("EndToEnd", endRange);
24633				        range.select();
24634					}
24635
24636					return node;
24637
24638				},
24639
24640				// #backward
24641				nodeToCaretPositionFromPoint: function(e, node)
24642				{
24643					this.insert.nodeToPoint(e, node);
24644				},
24645				marker: function()
24646				{
24647					this.marker.insert();
24648				}
24649			};
24650		},
24651
24652		// =keydown
24653		keydown: function()
24654		{
24655			return {
24656				init: function(e)
24657				{
24658					if (this.rtePaste)
24659					{
24660						return;
24661					}
24662
24663					var key = e.which;
24664					var arrow = (key >= 37 && key <= 40);
24665
24666					this.keydown.ctrl = e.ctrlKey || e.metaKey;
24667					this.keydown.parent = this.selection.parent();
24668					this.keydown.current = this.selection.current();
24669					this.keydown.block = this.selection.block();
24670
24671			        // detect tags
24672					this.keydown.pre = this.utils.isTag(this.keydown.current, 'pre');
24673					this.keydown.blockquote = this.utils.isTag(this.keydown.current, 'blockquote');
24674					this.keydown.figcaption = this.utils.isTag(this.keydown.current, 'figcaption');
24675					this.keydown.figure = this.utils.isTag(this.keydown.current, 'figure');
24676
24677					// callback
24678					var keydownStop = this.core.callback('keydown', e);
24679					if (keydownStop === false)
24680					{
24681						e.preventDefault();
24682						return false;
24683					}
24684
24685					// shortcuts setup
24686					this.shortcuts.init(e, key);
24687
24688					// buffer
24689					this.keydown.checkEvents(arrow, key);
24690                    this.keydown.setupBuffer(e, key);
24691
24692					if (this.utils.isSelectAll() && ( key === this.keyCode.ENTER || key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE))
24693					{
24694						e.preventDefault();
24695
24696						this.code.set(this.opts.emptyHtml);
24697                        this.events.changeHandler();
24698						return;
24699					}
24700
24701					this.keydown.addArrowsEvent(arrow);
24702					this.keydown.setupSelectAll(e, key);
24703
24704					// turn off enter key
24705					if (!this.opts.enterKey && key === this.keyCode.ENTER)
24706					{
24707						e.preventDefault();
24708
24709						// remove selected
24710						var sel = this.selection.get();
24711						var range = this.selection.range(sel);
24712
24713						if (!range.collapsed)
24714						{
24715							range.deleteContents();
24716						}
24717
24718						return;
24719					}
24720
24721					// down
24722					if (this.opts.enterKey && key === this.keyCode.DOWN)
24723					{
24724						this.keydown.onArrowDown();
24725					}
24726
24727					// up
24728					if (this.opts.enterKey && key === this.keyCode.UP)
24729					{
24730						this.keydown.onArrowUp();
24731					}
24732
24733
24734					// replace to p before / after the table or into body
24735					if ((this.opts.type === 'textarea' || this.opts.type === 'div') && this.keydown.current && this.keydown.current.nodeType === 3 && $(this.keydown.parent).hasClass('redactor-in'))
24736					{
24737						this.keydown.wrapToParagraph();
24738					}
24739
24740					// on Shift+Space or Ctrl+Space
24741					if (!this.keyup.lastShiftKey && key === this.keyCode.SPACE && (e.ctrlKey || e.shiftKey))
24742					{
24743						e.preventDefault();
24744
24745						return this.keydown.onShiftSpace();
24746					}
24747
24748					// on Shift+Enter or Ctrl+Enter
24749					if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey))
24750					{
24751						e.preventDefault();
24752
24753						return this.keydown.onShiftEnter(e);
24754					}
24755
24756					// on enter
24757					if (key === this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
24758					{
24759						return this.keydown.onEnter(e);
24760					}
24761
24762					// tab or cmd + [
24763					if (key === this.keyCode.TAB || e.metaKey && key === 221 || e.metaKey && key === 219)
24764					{
24765						return this.keydown.onTab(e, key);
24766					}
24767
24768                    // firefox bugfix
24769					if (this.detect.isFirefox() && key === this.keyCode.BACKSPACE && this.keydown.block && this.keydown.block.tagName === 'P' && this.utils.isStartOfElement(this.keydown.block))
24770					{
24771    					var $prev = $(this.keydown.block).prev();
24772    					if ($prev.length !== 0)
24773    					{
24774        					e.preventDefault();
24775
24776        					$prev.append(this.marker.get());
24777        					$prev.append($(this.keydown.block).html());
24778        					$(this.keydown.block).remove();
24779
24780                            this.selection.restore();
24781
24782        					return;
24783    					}
24784					}
24785
24786					// backspace & delete
24787					if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
24788					{
24789    					if (this.observe.image && typeof this.observe.image !== 'undefined' && $('#redactor-image-box').length !== 0)
24790    					{
24791        					e.preventDefault();
24792
24793        					var $prev = this.observe.image.closest('figure, p').prev()
24794        					this.image.remove(false);
24795                            this.observe.image = false;
24796
24797        					if ($prev && $prev.length !== 0)
24798        					{
24799            					this.caret.end($prev);
24800        					}
24801        					else
24802        					{
24803            					this.core.editor().focus();
24804        					}
24805
24806        					return;
24807    					}
24808
24809						this.keydown.onBackspaceAndDeleteBefore();
24810					}
24811
24812					if (key === this.keyCode.DELETE)
24813					{
24814						var $next = $(this.keydown.block).next();
24815
24816						// delete figure
24817						if (this.utils.isEndOfElement(this.keydown.block) && $next.length !== 0 && $next[0].tagName === 'FIGURE')
24818						{
24819							$next.remove();
24820							return false;
24821						}
24822
24823						// append list (safari bug)
24824						var tagLi = (this.keydown.block && this.keydown.block.tagName === 'LI') ? this.keydown.block : false;
24825						if (tagLi)
24826						{
24827							var $list = $(this.keydown.block).parents('ul, ol').last();
24828							var $nextList = $list.next();
24829
24830							if (this.utils.isRedactorParent($list) && this.utils.isEndOfElement($list) && $nextList.length !== 0
24831								&& ($nextList[0].tagName === 'UL' || $nextList[0].tagName === 'OL'))
24832							{
24833								e.preventDefault();
24834
24835								$list.append($nextList.contents());
24836								$nextList.remove();
24837
24838								return false;
24839							}
24840						}
24841
24842						// append pre
24843						if (this.utils.isEndOfElement(this.keydown.block) && $next.length !== 0 && $next[0].tagName === 'PRE')
24844						{
24845							$(this.keydown.block).append($next.text());
24846							$next.remove();
24847							return false;
24848						}
24849
24850					}
24851
24852					// image delete
24853					if (key === this.keyCode.DELETE && $('#redactor-image-box').length !== 0)
24854					{
24855						this.image.remove();
24856					}
24857
24858					// backspace
24859					if (key === this.keyCode.BACKSPACE)
24860					{
24861						if (this.detect.isFirefox())
24862						{
24863							this.line.removeOnBackspace(e);
24864						}
24865
24866                        // combine list after and before if paragraph is empty
24867                        if (this.list.combineAfterAndBefore(this.keydown.block))
24868                        {
24869                            e.preventDefault();
24870                            return;
24871                        }
24872
24873						// backspace as outdent
24874						var block = this.selection.block();
24875						if (block && block.tagName === 'LI' && this.utils.isCollapsed() && this.utils.isStartOfElement())
24876						{
24877							this.indent.decrease();
24878							e.preventDefault();
24879							return;
24880						}
24881
24882						this.keydown.removeInvisibleSpace();
24883						this.keydown.removeEmptyListInTable(e);
24884
24885					}
24886
24887					if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
24888					{
24889						this.keydown.onBackspaceAndDeleteAfter(e);
24890					}
24891
24892				},
24893				onShiftSpace: function()
24894				{
24895					this.buffer.set();
24896					this.insert.raw('&nbsp;');
24897
24898					return false;
24899				},
24900				onShiftEnter: function(e)
24901				{
24902					this.buffer.set();
24903
24904					return (this.keydown.pre) ? this.keydown.insertNewLine(e) : this.insert.raw('<br>');
24905				},
24906				onBackspaceAndDeleteBefore: function()
24907				{
24908					this.utils.saveScroll();
24909				},
24910				onBackspaceAndDeleteAfter: function(e)
24911				{
24912					// remove style tag
24913					setTimeout($.proxy(function()
24914					{
24915						this.code.syncFire = false;
24916						this.keydown.removeEmptyLists();
24917
24918                        var filter = '';
24919                        if (this.opts.keepStyleAttr.length !== 0)
24920                        {
24921                            filter = ',' + this.opts.keepStyleAttr.join(',');
24922                        }
24923
24924						var $styleTags = this.core.editor().find('*[style]');
24925						$styleTags.not('img, figure, iframe, #redactor-image-box, #redactor-image-editter, [data-redactor-style-cache], [data-redactor-span]' + filter).removeAttr('style');
24926
24927						this.keydown.formatEmpty(e);
24928						this.code.syncFire = true;
24929
24930					}, this), 1);
24931				},
24932				onEnter: function(e)
24933				{
24934					var stop = this.core.callback('enter', e);
24935					if (stop === false)
24936					{
24937						e.preventDefault();
24938						return false;
24939					}
24940
24941					// blockquote exit
24942					if (this.keydown.blockquote && this.keydown.exitFromBlockquote(e) === true)
24943					{
24944						return false;
24945					}
24946
24947					// pre
24948					if (this.keydown.pre)
24949					{
24950						return this.keydown.insertNewLine(e);
24951					}
24952					// blockquote & figcaption
24953					else if (this.keydown.blockquote || this.keydown.figcaption)
24954					{
24955						return this.keydown.insertBreakLine(e);
24956					}
24957					// figure
24958					else if (this.keydown.figure)
24959					{
24960						setTimeout($.proxy(function()
24961						{
24962							this.keydown.replaceToParagraph('FIGURE');
24963
24964						}, this), 1);
24965					}
24966					// paragraphs
24967					else if (this.keydown.block)
24968					{
24969						setTimeout($.proxy(function()
24970						{
24971							this.keydown.replaceToParagraph('DIV');
24972
24973						}, this), 1);
24974
24975						// empty list exit
24976						if (this.keydown.block.tagName === 'LI')
24977						{
24978							var current = this.selection.current();
24979							var $parent = $(current).closest('li', this.$editor[0]);
24980							var $list = $parent.parents('ul,ol', this.$editor[0]).last();
24981
24982							if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0 && this.utils.isEmpty($list.find("li").last().html()))
24983							{
24984								$list.find("li").last().remove();
24985
24986								var node = $(this.opts.emptyHtml);
24987								$list.after(node);
24988								this.caret.start(node);
24989
24990								return false;
24991							}
24992						}
24993
24994					}
24995					// outside
24996					else if (!this.keydown.block)
24997					{
24998						return this.keydown.insertParagraph(e);
24999					}
25000
25001                    // firefox enter into inline element
25002					if (this.detect.isFirefox() && this.utils.isInline(this.keydown.parent))
25003					{
25004                        this.keydown.insertBreakLine(e);
25005                        return;
25006                    }
25007
25008					// remove inline tags in new-empty paragraph
25009                    if (!this.opts.keepInlineOnEnter)
25010                    {
25011    					setTimeout($.proxy(function()
25012    					{
25013    						var inline = this.selection.inline();
25014    						if (inline && this.utils.isEmpty(inline.innerHTML))
25015    						{
25016    							var parent = this.selection.block();
25017    							$(inline).remove();
25018    							//this.caret.start(parent);
25019
25020                                var range = document.createRange();
25021                                range.setStart(parent, 0);
25022
25023                                var textNode = document.createTextNode('\u200B');
25024
25025                                range.insertNode(textNode);
25026                                range.setStartAfter(textNode);
25027                                range.collapse(true);
25028
25029                                var sel = window.getSelection();
25030                				sel.removeAllRanges();
25031                				sel.addRange(range);
25032    						}
25033
25034
25035    					}, this), 1);
25036					}
25037				},
25038				checkEvents: function(arrow, key)
25039				{
25040					if (!arrow && (this.core.getEvent() === 'click' || this.core.getEvent() === 'arrow'))
25041					{
25042						this.core.addEvent(false);
25043
25044						if (this.keydown.checkKeyEvents(key))
25045						{
25046							this.buffer.set();
25047						}
25048					}
25049				},
25050				checkKeyEvents: function(key)
25051				{
25052					var k = this.keyCode;
25053					var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
25054
25055					return ($.inArray(key, keys) === -1) ? true : false;
25056
25057				},
25058				addArrowsEvent: function(arrow)
25059				{
25060					if (!arrow)
25061					{
25062						return;
25063					}
25064
25065					if ((this.core.getEvent() === 'click' || this.core.getEvent() === 'arrow'))
25066					{
25067						this.core.addEvent(false);
25068						return;
25069					}
25070
25071				    this.core.addEvent('arrow');
25072				},
25073				setupBuffer: function(e, key)
25074				{
25075					if (this.keydown.ctrl && key === 90 && !e.shiftKey && !e.altKey && this.sBuffer.length) // z key
25076					{
25077						e.preventDefault();
25078						this.buffer.undo();
25079						return;
25080					}
25081					// redo
25082					else if (this.keydown.ctrl && key === 90 && e.shiftKey && !e.altKey && this.sRebuffer.length !== 0)
25083					{
25084						e.preventDefault();
25085						this.buffer.redo();
25086						return;
25087					}
25088					else if (!this.keydown.ctrl)
25089					{
25090						if (key === this.keyCode.SPACE || key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE || (key === this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey))
25091						{
25092							this.buffer.set();
25093						}
25094					}
25095				},
25096				exitFromBlockquote: function(e)
25097				{
25098					if (!this.utils.isEndOfElement(this.keydown.blockquote))
25099					{
25100						return;
25101					}
25102
25103					var tmp = this.clean.removeSpacesHard($(this.keydown.blockquote).html());
25104					if (tmp.search(/(<br\s?\/?>){1}$/i) !== -1)
25105					{
25106						e.preventDefault();
25107
25108						var $last = $(this.keydown.blockquote).children().last();
25109
25110						$last.filter('br').remove();
25111						$(this.keydown.blockquote).children().last().filter('span').remove();
25112
25113						var node = $(this.opts.emptyHtml);
25114						$(this.keydown.blockquote).after(node);
25115						this.caret.start(node);
25116
25117						return true;
25118
25119					}
25120
25121					return;
25122				},
25123				onArrowDown: function()
25124				{
25125					var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption];
25126
25127					for (var i = 0; i < tags.length; i++)
25128					{
25129						if (tags[i])
25130						{
25131							this.keydown.insertAfterLastElement(tags[i]);
25132							return false;
25133						}
25134					}
25135				},
25136				onArrowUp: function()
25137				{
25138					var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption];
25139
25140					for (var i = 0; i < tags.length; i++)
25141					{
25142						if (tags[i])
25143						{
25144							this.keydown.insertBeforeFirstElement(tags[i]);
25145							return false;
25146						}
25147					}
25148				},
25149				insertAfterLastElement: function(element)
25150				{
25151					if (!this.utils.isEndOfElement(element))
25152					{
25153						return;
25154					}
25155
25156					var last = this.core.editor().contents().last();
25157					var $next = (element.tagName === 'FIGCAPTION') ? $(this.keydown.block).parent().next() : $(this.keydown.block).next();
25158
25159					if ($next.length !== 0)
25160					{
25161						return;
25162					}
25163					else if (last.length === 0 && last[0] !== element)
25164					{
25165						this.caret.start(last);
25166						return;
25167					}
25168					else
25169					{
25170						var node = $(this.opts.emptyHtml);
25171
25172						if (element.tagName === 'FIGCAPTION')
25173						{
25174						    $(element).parent().after(node);
25175						}
25176						else
25177						{
25178    						$(element).after(node);
25179						}
25180
25181						this.caret.start(node);
25182					}
25183
25184				},
25185				insertBeforeFirstElement: function(element)
25186				{
25187					if (!this.utils.isStartOfElement())
25188					{
25189						return;
25190					}
25191
25192					if (this.core.editor().contents().length > 1 && this.core.editor().contents().first()[0] !== element)
25193					{
25194						return;
25195					}
25196
25197					var node = $(this.opts.emptyHtml);
25198					$(element).before(node);
25199					this.caret.start(node);
25200
25201				},
25202				onTab: function(e, key)
25203				{
25204					if (!this.opts.tabKey)
25205					{
25206						return true;
25207					}
25208
25209					var isList = (this.keydown.block && this.keydown.block.tagName === 'LI')
25210					if (this.utils.isEmpty(this.code.get()) || (!isList && !this.keydown.pre && this.opts.tabAsSpaces === false))
25211					{
25212						return true;
25213					}
25214
25215					e.preventDefault();
25216					this.buffer.set();
25217
25218                    var isListStart = (isList && this.utils.isStartOfElement(this.keydown.block));
25219					var node;
25220
25221					if (this.keydown.pre && !e.shiftKey)
25222					{
25223						node = (this.opts.preSpaces) ? document.createTextNode(Array(this.opts.preSpaces + 1).join('\u00a0')) : document.createTextNode('\t');
25224						this.insert.node(node);
25225					}
25226					else if (this.opts.tabAsSpaces !== false && !isListStart)
25227					{
25228						node = document.createTextNode(Array(this.opts.tabAsSpaces + 1).join('\u00a0'));
25229						this.insert.node(node);
25230					}
25231					else
25232					{
25233						if (e.metaKey && key === 219)
25234						{
25235							this.indent.decrease();
25236						}
25237						else if (e.metaKey && key === 221)
25238						{
25239							this.indent.increase();
25240						}
25241						else if (!e.shiftKey)
25242						{
25243							this.indent.increase();
25244						}
25245						else
25246						{
25247							this.indent.decrease();
25248						}
25249					}
25250
25251					return false;
25252				},
25253				setupSelectAll: function(e, key)
25254				{
25255					if (this.keydown.ctrl && key === 65)
25256					{
25257						this.utils.enableSelectAll();
25258					}
25259					else if (key !== this.keyCode.LEFT_WIN && !this.keydown.ctrl)
25260					{
25261						this.utils.disableSelectAll();
25262					}
25263				},
25264				insertNewLine: function(e)
25265				{
25266					e.preventDefault();
25267
25268					var node = document.createTextNode('\n');
25269
25270					var sel = this.selection.get();
25271					var range = this.selection.range(sel);
25272
25273					range.deleteContents();
25274					range.insertNode(node);
25275
25276					this.caret.after(node);
25277
25278					return false;
25279				},
25280				insertParagraph: function(e)
25281				{
25282					e.preventDefault();
25283
25284					var p = document.createElement('p');
25285					//p.innerHTML = this.opts.invisibleSpace;
25286					p.innerHTML = '<br>';
25287
25288					var sel = this.selection.get();
25289					var range = this.selection.range(sel);
25290
25291					range.deleteContents();
25292					range.insertNode(p);
25293
25294					this.caret.start(p);
25295
25296					return false;
25297				},
25298				insertBreakLine: function(e)
25299				{
25300					return this.keydown.insertBreakLineProcessing(e);
25301				},
25302				insertDblBreakLine: function(e)
25303				{
25304					return this.keydown.insertBreakLineProcessing(e, true);
25305				},
25306				insertBreakLineProcessing: function(e, dbl)
25307				{
25308					e.stopPropagation();
25309
25310					var br1 = document.createElement('br');
25311					this.insert.node(br1);
25312
25313					if (dbl === true)
25314					{
25315						var br2 = document.createElement('br');
25316						this.insert.node(br2);
25317						this.caret.after(br2);
25318					}
25319					else
25320					{
25321    					this.caret.after(br1);
25322					}
25323
25324					return false;
25325
25326				},
25327				wrapToParagraph: function()
25328				{
25329					var $current = $(this.keydown.current);
25330					var	node = $('<p>').append($current.clone());
25331					$current.replaceWith(node);
25332
25333					var next = $(node).next();
25334					if (typeof(next[0]) !== 'undefined' && next[0].tagName === 'BR')
25335					{
25336						next.remove();
25337					}
25338
25339					this.caret.end(node);
25340
25341				},
25342				replaceToParagraph: function(tag)
25343				{
25344					var blockElem = this.selection.block();
25345					var $prev = $(blockElem).prev();
25346
25347					var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
25348					if (blockElem.tagName === tag && this.utils.isEmpty(blockHtml) && !$(blockElem).hasClass('redactor-in'))
25349					{
25350						var p = document.createElement('p');
25351						$(blockElem).replaceWith(p);
25352
25353                        this.keydown.setCaretToParagraph(p);
25354
25355						return false;
25356					}
25357					else if (blockElem.tagName === 'P')
25358					{
25359						$(blockElem).removeAttr('class').removeAttr('style');
25360
25361						// fix #227
25362						if (this.detect.isIe() && this.utils.isEmpty(blockHtml) && this.utils.isInline(this.keydown.parent))
25363                        {
25364                            $(blockElem).on('input', $.proxy(function()
25365                            {
25366                                var parent = this.selection.parent();
25367                                if (this.utils.isInline(parent))
25368                                {
25369                                    var html = $(parent).html();
25370                                    $(blockElem).html(html);
25371                                    this.caret.end(blockElem);
25372                                }
25373
25374                                $(blockElem).off('keyup');
25375
25376                            }, this));
25377						}
25378
25379						return false;
25380					}
25381					else if ($prev.hasClass(this.opts.videoContainerClass))
25382					{
25383    					$prev.removeAttr('class');
25384
25385    					var p = document.createElement('p');
25386						$prev.replaceWith(p);
25387
25388						this.keydown.setCaretToParagraph(p);
25389
25390						return false;
25391					}
25392				},
25393				setCaretToParagraph: function(p)
25394				{
25395                    var range = document.createRange();
25396                    range.setStart(p, 0);
25397
25398                    var textNode = document.createTextNode('\u200B');
25399
25400                    range.insertNode(textNode);
25401                    range.setStartAfter(textNode);
25402                    range.collapse(true);
25403
25404                    var sel = window.getSelection();
25405    				sel.removeAllRanges();
25406    				sel.addRange(range);
25407				},
25408				removeInvisibleSpace: function()
25409				{
25410					var $current = $(this.keydown.current);
25411					if ($current.text().search(/^\u200B$/g) === 0)
25412					{
25413						$current.remove();
25414					}
25415				},
25416				removeEmptyListInTable: function(e)
25417				{
25418					var $current = $(this.keydown.current);
25419					var $parent = $(this.keydown.parent);
25420					var td = $current.closest('td', this.$editor[0]);
25421
25422					if (td.length !== 0 && $current.closest('li', this.$editor[0]) && $parent.children('li').length === 1)
25423					{
25424						if (!this.utils.isEmpty($current.text()))
25425						{
25426							return;
25427						}
25428
25429						e.preventDefault();
25430
25431						$current.remove();
25432						$parent.remove();
25433
25434						this.caret.start(td);
25435					}
25436				},
25437				removeEmptyLists: function()
25438				{
25439					var removeIt = function()
25440					{
25441						var html = $.trim(this.innerHTML).replace(/\/t\/n/g, '');
25442						if (html === '')
25443						{
25444							$(this).remove();
25445						}
25446					};
25447
25448					this.core.editor().find('li').each(removeIt);
25449					this.core.editor().find('ul, ol').each(removeIt);
25450				},
25451				formatEmpty: function(e)
25452				{
25453					var html = $.trim(this.core.editor().html());
25454
25455					if (!this.utils.isEmpty(html))
25456					{
25457						return;
25458					}
25459
25460					e.preventDefault();
25461
25462					if (this.opts.type === 'inline' || this.opts.type === 'pre')
25463					{
25464						this.core.editor().html(this.marker.html());
25465						this.selection.restore();
25466					}
25467					else
25468					{
25469						this.core.editor().html(this.opts.emptyHtml);
25470						this.focus.start();
25471					}
25472
25473					return false;
25474
25475				}
25476			};
25477		},
25478
25479		// =keyup
25480		keyup: function()
25481		{
25482			return {
25483				init: function(e)
25484				{
25485					if (this.rtePaste)
25486					{
25487						return;
25488					}
25489
25490					var key = e.which;
25491					this.keyup.block = this.selection.block();
25492					this.keyup.current = this.selection.current();
25493					this.keyup.parent = this.selection.parent();
25494					this.keyup.lastShiftKey = e.shiftKey;
25495
25496
25497					// callback
25498					var stop = this.core.callback('keyup', e);
25499					if (stop === false)
25500					{
25501						e.preventDefault();
25502						return false;
25503					}
25504
25505                    // replace a prev figure to paragraph if caret is before image
25506                    if (key === this.keyCode.ENTER)
25507                    {
25508                        if (this.keyup.block && this.keyup.block.tagName === 'FIGURE')
25509                        {
25510                            var $prev = $(this.keyup.block).prev();
25511                            if ($prev.length !== 0 && $prev[0].tagName === 'FIGURE')
25512                            {
25513                                var $newTag = this.utils.replaceToTag($prev, 'p');
25514                                this.caret.start($newTag);
25515                                return;
25516                            }
25517                        }
25518                    }
25519
25520					// replace figure to paragraph
25521					if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
25522					{
25523						if (this.utils.isSelectAll())
25524						{
25525							this.focus.start();
25526                            this.toolbar.setUnfixed();
25527
25528							return;
25529						}
25530
25531						// if caret before figure - delete image
25532						if (this.keyup.block && this.keydown.block && this.keyup.block.tagName === 'FIGURE' && this.utils.isStartOfElement(this.keydown.block))
25533						{
25534    						e.preventDefault();
25535
25536                            this.selection.save();
25537                            $(this.keyup.block).find('figcaption').remove();
25538    						$(this.keyup.block).find('img').first().remove();
25539    						this.utils.replaceToTag(this.keyup.block, 'p');
25540
25541    						var $marker = this.marker.find();
25542    						$('html, body').animate({ scrollTop: $marker.position().top + 20 }, 500);
25543
25544    						this.selection.restore();
25545    						return;
25546						}
25547
25548
25549						// if paragraph does contain only image replace to figure
25550						if (this.keyup.block && this.keyup.block.tagName === 'P')
25551						{
25552							var isContainImage = $(this.keyup.block).find('img').length;
25553							var text = $(this.keyup.block).text().replace(/\u200B/g, '');
25554							if (text === '' && isContainImage !== 0)
25555							{
25556								this.utils.replaceToTag(this.keyup.block, 'figure');
25557							}
25558						}
25559
25560						// if figure does not contain image - replace to paragraph
25561						if (this.keyup.block && this.keyup.block.tagName === 'FIGURE' && $(this.keyup.block).find('img').length === 0)
25562						{
25563							this.selection.save();
25564							this.utils.replaceToTag(this.keyup.block, 'p');
25565							this.selection.restore();
25566						}
25567					}
25568
25569					// linkify
25570					if (this.linkify.isKey(key))
25571					{
25572    					this.selection.save();
25573						this.linkify.format();
25574    					this.selection.restore();
25575					}
25576
25577				}
25578
25579			};
25580		},
25581
25582		// =lang
25583		lang: function()
25584		{
25585			return {
25586				load: function()
25587				{
25588					this.opts.curLang = this.opts.langs[this.opts.lang];
25589				},
25590				get: function(name)
25591				{
25592					return (typeof this.opts.curLang[name] !== 'undefined') ? this.opts.curLang[name] : '';
25593				}
25594			};
25595		},
25596
25597		// =line
25598		line: function()
25599		{
25600			return {
25601				insert: function()
25602				{
25603					this.buffer.set();
25604
25605					// insert
25606					this.insert.html(this.line.getLineHtml());
25607
25608					// find
25609					var $hr = this.core.editor().find('#redactor-hr-tmp-id');
25610					$hr.removeAttr('id');
25611
25612					this.core.callback('insertedLine', $hr);
25613
25614					return $hr;
25615				},
25616				getLineHtml: function()
25617				{
25618					var html = '<hr id="redactor-hr-tmp-id" />';
25619					if (!this.detect.isFirefox() && this.utils.isEmpty())
25620					{
25621						html += '<p>' + this.opts.emptyHtml + '</p>';
25622					}
25623
25624					return html;
25625				},
25626				// ff only
25627				removeOnBackspace: function(e)
25628				{
25629					if (!this.utils.isCollapsed())
25630					{
25631						return;
25632					}
25633
25634					var $block = $(this.selection.block());
25635					if ($block.length === 0 || !this.utils.isStartOfElement($block))
25636					{
25637						return;
25638					}
25639
25640					// if hr is previous element
25641					var $prev = $block.prev();
25642					if ($prev && $prev.length !== 0 && $prev[0].tagName === 'HR')
25643					{
25644						e.preventDefault();
25645						$prev.remove();
25646					}
25647				}
25648			};
25649		},
25650
25651		// =link
25652		link: function()
25653		{
25654			return {
25655
25656				// public
25657				get: function()
25658				{
25659					return $(this.selection.inlines('a'));
25660				},
25661				is: function()
25662				{
25663					var nodes = this.selection.nodes() ;
25664					var $link = $(this.selection.current()).closest('a', this.core.editor()[0]);
25665
25666					return ($link.length === 0 || nodes.length > 1) ? false : $link;
25667				},
25668				unlink: function(e)
25669				{
25670					// if call from clickable element
25671					if (typeof e !== 'undefined' && e.preventDefault)
25672					{
25673						e.preventDefault();
25674					}
25675
25676					// buffer
25677					this.buffer.set();
25678
25679					var links = this.selection.inlines('a');
25680					if (links.length === 0)
25681					{
25682						return;
25683					}
25684
25685					var $links = this.link.replaceLinksToText(links);
25686
25687					this.observe.closeAllTooltip();
25688					this.core.callback('deletedLink', $links);
25689
25690				},
25691				insert: function(link, cleaned)
25692				{
25693					var $el = this.link.is();
25694
25695					if (cleaned !== true)
25696					{
25697						link = this.link.buildLinkFromObject($el, link);
25698						if (link === false)
25699						{
25700							return false;
25701						}
25702					}
25703
25704					// buffer
25705					this.buffer.set();
25706
25707					// callback
25708					link = this.core.callback('beforeInsertingLink', link);
25709
25710					if ($el === false)
25711					{
25712						// insert
25713						$el = $('<a />');
25714						$el = this.link.update($el, link);
25715						$el = $(this.insert.node($el));
25716
25717						var $parent = $el.parent();
25718						if (this.utils.isRedactorParent($parent) === false)
25719						{
25720							$el.wrap('<p>');
25721						}
25722
25723						// remove unlink wrapper
25724						if ($parent.hasClass('redactor-unlink'))
25725						{
25726							$parent.replaceWith(function(){
25727								return $(this).contents();
25728							});
25729						}
25730
25731						this.caret.after($el);
25732						this.core.callback('insertedLink', $el);
25733					}
25734					else
25735					{
25736						// update
25737						$el = this.link.update($el, link);
25738						this.caret.after($el);
25739					}
25740
25741					return $el;
25742
25743				},
25744				update: function($el, link)
25745				{
25746					$el.text(link.text);
25747					$el.attr('href', link.url);
25748
25749          this.link.button($el, link.button);
25750          this.link.target($el, link.target);
25751
25752					return $el;
25753
25754				},
25755        target: function($el, target)
25756				{
25757					return (target) ? $el.attr('target', '_blank') : $el.removeAttr('target');
25758				},
25759        button: function($el, button)
25760				{
25761					return (button) ? $el.addClass('button') : $el.removeClass('button');
25762				},
25763				show: function(e)
25764				{
25765					// if call from clickable element
25766					if (typeof e !== 'undefined' && e.preventDefault)
25767					{
25768						e.preventDefault();
25769					}
25770
25771					// close tooltip
25772					this.observe.closeAllTooltip();
25773
25774					// is link
25775					var $el = this.link.is();
25776
25777					// build modal
25778					this.link.buildModal($el);
25779
25780					// build link
25781					var link = this.link.buildLinkFromElement($el);
25782
25783					// if link cut & paste inside editor browser added self host to a link
25784					link.url = this.link.removeSelfHostFromUrl(link.url);
25785
25786                    // new tab target
25787					if (this.opts.linkNewTab && !$el)
25788					{
25789    					link.target = true;
25790					}
25791
25792					// set modal values
25793					this.link.setModalValues(link);
25794
25795					// show modal
25796					this.modal.show();
25797
25798					// focus
25799					if (this.detect.isDesktop())
25800					{
25801						$('#redactor-link-url').focus();
25802					}
25803				},
25804
25805				// private
25806				setModalValues: function(link)
25807				{
25808          $('#redactor-link-blank').prop('checked', link.target);
25809          $('#redactor-link-button').prop('checked', link.button);
25810					$('#redactor-link-url').val(link.url);
25811					$('#redactor-link-url-text').val(link.text);
25812				},
25813				buildModal: function($el)
25814				{
25815					this.modal.load('link', this.lang.get(($el === false) ? 'link-insert' : 'link-edit'), 600);
25816
25817					// button insert
25818					var $btn = this.modal.getActionButton();
25819					$btn.text(this.lang.get(($el === false) ? 'insert' : 'save')).on('click', $.proxy(this.link.callback, this));
25820
25821				},
25822				callback: function()
25823				{
25824					// build link
25825					var link = this.link.buildLinkFromModal();
25826					if (link === false)
25827					{
25828						return false;
25829					}
25830
25831					// close
25832					this.modal.close();
25833
25834					// insert or update
25835					this.link.insert(link, true);
25836				},
25837				cleanUrl: function(url)
25838				{
25839					return (typeof url === 'undefined') ? '' : $.trim(url.replace(/[^\W\w\D\d+&\'@#/%?=~_|!:,.;\(\)]/gi, ''));
25840				},
25841				cleanText: function(text)
25842				{
25843					return (typeof text === 'undefined') ? '' : $.trim(text.replace(/(<([^>]+)>)/gi, ''));
25844				},
25845				getText: function(link)
25846				{
25847					return (link.text === '' && link.url !== '') ? this.link.truncateUrl(link.url.replace(/<|>/g, '')) : link.text;
25848				},
25849				isUrl: function(url)
25850				{
25851    				var reUrl = new RegExp('^((https?|ftp):\\/\\/)?(([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$','i');
25852
25853    				return (reUrl.test(url)) ? url : false;
25854				},
25855				isMailto: function(url)
25856				{
25857					return (url.search('@') !== -1 && /(http|ftp|https):\/\//i.test(url) === false);
25858				},
25859				isEmpty: function(link)
25860				{
25861					return (link.url === '' || (link.text === '' && link.url === ''));
25862				},
25863				truncateUrl: function(url)
25864				{
25865					return (url.length > this.opts.linkSize) ? url.substring(0, this.opts.linkSize) + '...' : url;
25866				},
25867				parse: function(link)
25868				{
25869					// mailto
25870					if (this.link.isMailto(link.url))
25871					{
25872						link.url = 'mailto:' + link.url.replace('mailto:', '');
25873					}
25874					// url
25875					else if (link.url.search('#') !== 0)
25876					{
25877    					if (this.opts.linkValidation)
25878    					{
25879        					link.url = (this.link.isUrl(link.url)) ? 'http://' + link.url.replace(/(ftp|https?):\/\//gi, '') : link.url;
25880						}
25881					}
25882
25883					// empty url or text or isn't url
25884					return (this.link.isEmpty(link) || link.url === false) ? false : link;
25885
25886				},
25887				buildLinkFromModal: function()
25888				{
25889					var link = {};
25890
25891					// url
25892					link.url = this.link.cleanUrl($('#redactor-link-url').val());
25893
25894					// text
25895					link.text = this.link.cleanText($('#redactor-link-url-text').val());
25896					link.text = this.link.getText(link);
25897
25898          // button
25899          link.button = ($('#redactor-link-button').prop('checked')) ? true : false;
25900
25901					// target
25902					link.target = ($('#redactor-link-blank').prop('checked')) ? true : false;
25903
25904					// parse
25905					return this.link.parse(link);
25906
25907				},
25908				buildLinkFromObject: function($el, link)
25909				{
25910					// url
25911					link.url = this.link.cleanUrl(link.url);
25912
25913					// text
25914					link.text = (typeof link.text === 'undefined' && this.selection.is()) ? this.selection.text() : this.link.cleanText(link.text);
25915					link.text = this.link.getText(link);
25916
25917          // button
25918					link.button = ($el === false) ? link.button : this.link.buildButton($el);
25919
25920          // target
25921					link.target = ($el === false) ? link.target : this.link.buildTarget($el);
25922
25923					// parse
25924					return this.link.parse(link);
25925
25926				},
25927				buildLinkFromElement: function($el)
25928				{
25929					var link = {
25930						url: '',
25931						text: (this.selection.is()) ? this.selection.text() : '',
25932						target: false
25933					};
25934
25935					if ($el !== false)
25936					{
25937						link.url = $el.attr('href');
25938						link.text = $el.text();
25939            link.button = this.link.buildButton($el);
25940            link.target = this.link.buildTarget($el);
25941					}
25942
25943					return link;
25944				},
25945        buildButton: function($el)
25946				{
25947          console.log('build button');
25948					return $el.hasClass('button') ? true : false;
25949				},
25950        buildTarget: function($el)
25951				{
25952          console.log('build target');
25953					return (typeof $el.attr('target') !== 'undefined' && $el.attr('target') === '_blank') ? true : false;
25954				},
25955				removeSelfHostFromUrl: function(url)
25956				{
25957					var href = self.location.href.replace('#', '').replace(/\/$/i, '');
25958					return url.replace(/^\/\#/, '#').replace(href, '').replace('mailto:', '');
25959				},
25960				replaceLinksToText: function(links)
25961				{
25962					var $first;
25963					var $links = $.each(links, function(i,s)
25964					{
25965						var $el = $(s);
25966						var $unlinked = $('<span class="redactor-unlink" />').append($el.contents());
25967						$el.replaceWith($unlinked);
25968
25969						if (i === 0)
25970						{
25971							$first = $unlinked;
25972						}
25973
25974						return $el;
25975					});
25976
25977					// set caret after unlinked node
25978					if (links.length === 1 && this.selection.isCollapsed())
25979					{
25980						this.caret.after($first);
25981					}
25982
25983					return $links;
25984				}
25985			};
25986		},
25987
25988		// =linkify
25989		linkify: function()
25990		{
25991			return {
25992				isKey: function(key)
25993				{
25994					return key === this.keyCode.ENTER || key === this.keyCode.SPACE;
25995				},
25996				isLink: function(node)
25997				{
25998					return (node.nodeValue.match(this.opts.regexps.linkyoutube) || node.nodeValue.match(this.opts.regexps.linkvimeo) || node.nodeValue.match(this.opts.regexps.linkimage) || node.nodeValue.match(this.opts.regexps.url));
25999				},
26000				isFiltered: function(i, node)
26001				{
26002					return node.nodeType === 3 && $.trim(node.nodeValue) !== "" && !$(node).parent().is("pre") && (this.linkify.isLink(node));
26003				},
26004				handler: function(i, node)
26005				{
26006					var $el = $(node);
26007					var text = $el.text();
26008					var html = text;
26009
26010					if (html.match(this.opts.regexps.linkyoutube) || html.match(this.opts.regexps.linkvimeo))
26011					{
26012						html = this.linkify.convertVideoLinks(html);
26013					}
26014					else if (html.match(this.opts.regexps.linkimage))
26015					{
26016						html = this.linkify.convertImages(html);
26017					}
26018					else
26019					{
26020						html = this.linkify.convertLinks(html);
26021					}
26022
26023					$el.before(text.replace(text, html)).remove();
26024				},
26025				format: function()
26026				{
26027					if (!this.opts.linkify || this.utils.isCurrentOrParent('pre'))
26028					{
26029						return;
26030					}
26031
26032					this.core.editor().find(":not(iframe,img,a,pre,code,.redactor-unlink)").addBack().contents().filter($.proxy(this.linkify.isFiltered, this)).each($.proxy(this.linkify.handler, this));
26033
26034					// collect
26035					var $el;
26036					var $objects = this.core.editor().find('.redactor-linkify-object').each($.proxy(function(i,s)
26037					{
26038						$el = $(s);
26039						$el.removeClass('redactor-linkify-object');
26040						if ($el.attr('class') === '')
26041						{
26042							$el.removeAttr('class');
26043						}
26044
26045						if (s.tagName === 'A')
26046						{
26047							this.core.callback('insertedLink', $el);
26048						}
26049
26050						return $el;
26051
26052					}, this));
26053
26054					// callback
26055					setTimeout($.proxy(function()
26056					{
26057						this.code.sync();
26058						this.core.callback('linkify', $objects);
26059
26060					}, this), 100);
26061
26062				},
26063				convertVideoLinks: function(html)
26064				{
26065					var iframeStart = '<div class="' + this.opts.videoContainerClass + ' redactor-linkify-object"><iframe class="redactor-linkify-object" width="500" height="281" src="';
26066					var iframeEnd = '" frameborder="0" allowfullscreen></iframe></div>';
26067
26068					if (html.match(this.opts.regexps.linkyoutube))
26069					{
26070						html = html.replace(this.opts.regexps.linkyoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
26071					}
26072
26073					if (html.match(this.opts.regexps.linkvimeo))
26074					{
26075						html = html.replace(this.opts.regexps.linkvimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
26076					}
26077
26078					return html;
26079				},
26080				convertImages: function(html)
26081				{
26082					var matches = html.match(this.opts.regexps.linkimage);
26083					if (!matches)
26084					{
26085						return html;
26086					}
26087
26088					return html.replace(html, '<img src="' + matches + '"  class="redactor-linkify-object" />');
26089				},
26090				convertLinks: function(html)
26091				{
26092					var matches = html.match(this.opts.regexps.url);
26093					if (!matches)
26094					{
26095						return html;
26096					}
26097
26098					matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; });
26099
26100					var length = matches.length;
26101
26102					for (var i = 0; i < length; i++)
26103					{
26104						var href = matches[i], text = href;
26105						var linkProtocol = (href.match(/(https?|ftp):\/\//i) !== null) ? '' : 'http://';
26106
26107						if (text.length > this.opts.linkSize)
26108						{
26109							text = text.substring(0, this.opts.linkSize) + '...';
26110						}
26111
26112						if (text.search('%') === -1)
26113						{
26114							text = decodeURIComponent(text);
26115						}
26116
26117						var regexB = "\\b";
26118
26119						if ($.inArray(href.slice(-1), ["/", "&", "="]) !== -1)
26120						{
26121							regexB = "";
26122						}
26123
26124						// escaping url
26125						var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g');
26126
26127						// target
26128						var target = '';
26129						if (this.opts.pasteLinkTarget !== false)
26130                        {
26131    					    target = ' target="' + this.opts.pasteLinkTarget + '"';
26132                        }
26133
26134						html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '"' + target + ' class="redactor-linkify-object">' + $.trim(text) + '</a>');
26135					}
26136
26137					return html;
26138				}
26139			};
26140		},
26141
26142		// =list
26143		list: function()
26144		{
26145			return {
26146				toggle: function(type)
26147				{
26148					if (this.utils.inBlocks(['table', 'td', 'th', 'tr']))
26149					{
26150						return;
26151					}
26152
26153    				type = (type === 'orderedlist') ? 'ol' : type;
26154    				type = (type === 'unorderedlist') ? 'ul' : type;
26155
26156    				type = type.toLowerCase();
26157
26158                    this.buffer.set();
26159                    this.selection.save();
26160
26161                    var nodes = this.list._getBlocks();
26162                    var block = this.selection.block();
26163                    var $list = $(block).parents('ul, ol').last();
26164                    if (nodes.length === 0 && $list.length !== 0)
26165                    {
26166                        nodes = [$list.get(0)];
26167                    }
26168
26169                    nodes = (this.list._isUnformat(type, nodes)) ? this.list._unformat(type, nodes) : this.list._format(type, nodes);
26170
26171                    this.selection.restore();
26172
26173                    return nodes;
26174				},
26175				get: function()
26176				{
26177					var current = this.selection.current();
26178					var $list = $(current).closest('ul, ol', this.core.editor()[0]);
26179
26180					return ($list.length === 0) ? false : $list;
26181				},
26182                combineAfterAndBefore: function(block)
26183				{
26184    				var $prev = $(block).prev();
26185    				var $next = $(block).next();
26186                    var isEmptyBlock = (block && block.tagName === 'P' && (block.innerHTML === '<br>' || block.innerHTML === ''));
26187                    var isBlockWrapped = ($prev.closest('ol, ul', this.core.editor()[0]).length === 1 && $next.closest('ol, ul', this.core.editor()[0]).length === 1);
26188
26189                    if (isEmptyBlock && isBlockWrapped)
26190                    {
26191                        $prev.children('li').last().append(this.marker.get());
26192                        $prev.append($next.contents());
26193                        this.selection.restore();
26194
26195                        return true;
26196                    }
26197
26198                    return false;
26199
26200				},
26201    			_getBlocks: function()
26202    			{
26203        			var finalBlocks = [];
26204        			var blocks = this.selection.blocks();
26205        			for (var i = 0; i < blocks.length; i++)
26206        			{
26207            			var $el = $(blocks[i]);
26208            			var isFirst = ($el.parent().hasClass('redactor-in'));
26209
26210            			if (isFirst) finalBlocks.push(blocks[i]);
26211        			}
26212
26213        			return finalBlocks;
26214    			},
26215                _isUnformat: function(type, nodes)
26216                {
26217                    var countLists = 0;
26218                    for (var i = 0; i < nodes.length; i++)
26219                    {
26220                        if (nodes[i].nodeType !== 3)
26221                        {
26222                            var tag = nodes[i].tagName.toLowerCase();
26223                            if (tag === type || tag === 'figure')
26224                            {
26225                                countLists++;
26226                            }
26227                        }
26228                    }
26229
26230                    return (countLists === nodes.length);
26231                },
26232                _uniteBlocks: function(nodes, tags)
26233                {
26234                    var z = 0;
26235                    var blocks = { 0: [] };
26236                    var lastcell = false;
26237                    for (var i = 0; i < nodes.length; i++)
26238                    {
26239                        var $node = $(nodes[i]);
26240                        var $cell = $node.closest('th, td');
26241
26242                        if ($cell.length !== 0)
26243                        {
26244                            if ($cell.get(0) !== lastcell)
26245                            {
26246                                // create block
26247                                z++;
26248                                blocks[z] = [];
26249                            }
26250
26251                            if (this.list._isUniteBlock(nodes[i], tags))
26252                            {
26253                                blocks[z].push(nodes[i]);
26254                            }
26255                        }
26256                        else
26257                        {
26258                            if (this.list._isUniteBlock(nodes[i], tags))
26259                            {
26260                                blocks[z].push(nodes[i]);
26261                            }
26262                            else
26263                            {
26264                                // create block
26265                                z++;
26266                                blocks[z] = [];
26267                            }
26268                        }
26269
26270                        lastcell = $cell.get();
26271                    }
26272
26273                    return blocks;
26274                },
26275                _isUniteBlock: function(node, tags)
26276                {
26277                    return (node.nodeType === 3 || tags.indexOf(node.tagName.toLowerCase()) !== -1);
26278                },
26279                _createList: function(type, blocks, key)
26280                {
26281                    var last = blocks[blocks.length-1];
26282                    var $last = $(last);
26283                    var $list = $('<' + type + '>');
26284                    $last.after($list);
26285
26286                    return $list;
26287                },
26288                _createListItem: function(item)
26289                {
26290                    var $item = $('<li>');
26291                    if (item.nodeType === 3)
26292                    {
26293                        $item.append(item);
26294                    }
26295                    else
26296                    {
26297                        var $el = $(item);
26298                        $item.append($el.contents());
26299                        $el.remove();
26300                    }
26301
26302                    return $item;
26303                },
26304                _format: function(type, nodes)
26305                {
26306                    var tags = ['p', 'div', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol'];
26307                    var blocks = this.list._uniteBlocks(nodes, tags);
26308                    var lists = [];
26309
26310                    for (var key in blocks)
26311                    {
26312                        var items = blocks[key];
26313                        var $list = this.list._createList(type, blocks[key]);
26314
26315                        for (var i = 0; i < items.length; i++)
26316                        {
26317                            var $item;
26318
26319                            // lists
26320                            if (items[i].nodeType !== 3 && (items[i].tagName === 'UL' || items[i].tagName === 'OL'))
26321                            {
26322                                $item = $(items[i]).contents();
26323                                $list.append($item);
26324                            }
26325                            // other blocks or texts
26326                            else
26327                            {
26328                                $item = this.list._createListItem(items[i]);
26329                                //this.utils.normalizeTextNodes($item);
26330                                $list.append($item);
26331                            }
26332                        }
26333
26334                        lists.push($list.get(0));
26335                    }
26336
26337                    return lists;
26338                },
26339                _unformat: function(type, nodes)
26340                {
26341
26342                    if (nodes.length === 1)
26343                    {
26344                        // one list
26345                        var $list = $(nodes[0]);
26346                        var $items = $list.find('li');
26347
26348                        var selectedItems = this.selection.blocks(['li']);
26349                        var block = this.selection.block();
26350                        var $li = $(block).closest('li');
26351                        if (selectedItems.length === 0 && $li.length !== 0)
26352                        {
26353                            selectedItems = [$li.get(0)];
26354                        }
26355
26356                        // 1) entire
26357                        if (selectedItems.length === $items.length)
26358                        {
26359                            return this.list._unformatEntire(nodes[0]);
26360                        }
26361
26362                        var pos = this.list._getItemsPosition($items, selectedItems);
26363
26364                        // 2) top
26365                        if (pos === 'Top')
26366                        {
26367                            return this.list._unformatAtSide('before', selectedItems, $list);
26368                        }
26369
26370                        // 3) bottom
26371                        else if (pos === 'Bottom')
26372                        {
26373                            selectedItems.reverse();
26374                            return this.list._unformatAtSide('after', selectedItems, $list);
26375                        }
26376
26377                        // 4) middle
26378                        else if (pos === 'Middle')
26379                        {
26380                            var $last = $(selectedItems[selectedItems.length-1]);
26381
26382                            var ci = false;
26383
26384                            var $parent = false;
26385                            var $secondList = $('<' + $list.get(0).tagName.toLowerCase() + '>');
26386                            $items.each(function(i, node)
26387                            {
26388                                if (ci)
26389                                {
26390                                    var $node = $(node);
26391                                    var $childList = ($node.children('ul, ol').length !== 0);
26392
26393                                    if ($node.closest('.redactor-split-item').length === 0 && ($parent === false || $node.closest($parent).length === 0))
26394                                    {
26395                                        $node.addClass('redactor-split-item');
26396                                    }
26397
26398                                    $parent = $node;
26399
26400                                }
26401
26402                                if (node === $last.get(0))
26403                                {
26404                                    ci = true;
26405                                }
26406                            });
26407
26408                            $items.filter('.redactor-split-item').each(function(i, node)
26409                            {
26410                                var $node = $(node);
26411                                $node.removeClass('redactor-split-item');
26412                                $secondList.append(node);
26413                            });
26414
26415                            $list.after($secondList);
26416
26417                            selectedItems.reverse();
26418                            for (var i = 0; i < selectedItems.length; i++)
26419                            {
26420                                var $item = $(selectedItems[i]);
26421                                var $container = this.list._createUnformatContainer($item);
26422
26423                                $list.after($container);
26424                                $container.find('ul, ol').remove();
26425                                $item.remove();
26426                            }
26427
26428
26429                            return;
26430                        }
26431
26432                    }
26433                    else
26434                    {
26435                        // unformat all
26436                        for (var i = 0; i < nodes.length; i++)
26437                        {
26438                            if (nodes[i].nodeType !== 3 && nodes[i].tagName.toLowerCase() === type)
26439                            {
26440                                this.list._unformatEntire(nodes[i]);
26441                            }
26442                        }
26443                    }
26444                },
26445                _unformatEntire: function(list)
26446                {
26447                    var $list = $(list);
26448                    var $items = $list.find('li');
26449                    $items.each(function(i, node)
26450                    {
26451                        var $item = $(node);
26452                        var $container = this.list._createUnformatContainer($item);
26453
26454                        $item.remove();
26455                        $list.before($container);
26456
26457                    }.bind(this));
26458
26459                    $list.remove();
26460                },
26461                _unformatAtSide: function(type, selectedItems, $list)
26462                {
26463                    for (var i = 0; i < selectedItems.length; i++)
26464                    {
26465                        var $item = $(selectedItems[i]);
26466                        var $container = this.list._createUnformatContainer($item);
26467
26468                        $list[type]($container);
26469
26470                        var $innerLists = $container.find('ul, ol').first();
26471                        $item.append($innerLists);
26472
26473                        $innerLists.each(function(i, node)
26474                        {
26475                            var $node = $(node);
26476                            var $parent = $node.closest('li');
26477
26478                            if ($parent.get(0) === selectedItems[i])
26479                            {
26480                                $node.unwrap();
26481                                $parent.addClass('r-unwrapped');
26482                            }
26483
26484                        });
26485
26486                        if (this.utils.isEmpty($item.html())) $item.remove();
26487                    }
26488
26489                    // clear empty
26490                    $list.find('.r-unwrapped').each(function(node)
26491                    {
26492                        var $node = $(node);
26493                        if ($node.html().trim() === '') $node.remove();
26494                        else $node.removeClass('r-unwrapped');
26495                    });
26496                },
26497                _getItemsPosition: function($items, selectedItems)
26498                {
26499                    var pos = 'Middle';
26500
26501                    var sFirst = selectedItems[0];
26502                    var sLast = selectedItems[selectedItems.length-1];
26503
26504                    var first = $items.first().get(0);
26505                    var last = $items.last().get(0);
26506
26507                    if (first === sFirst && last !== sLast)
26508                    {
26509                        pos = 'Top';
26510                    }
26511                    else if (first !== sFirst && last === sLast)
26512                    {
26513                        pos = 'Bottom';
26514                    }
26515
26516                    return pos;
26517                },
26518                _createUnformatContainer: function($item)
26519                {
26520                    var $container = $('<p>');
26521                    $container.append($item.contents());
26522
26523                    return $container;
26524                }
26525			};
26526		},
26527
26528		// =marker
26529		marker: function()
26530		{
26531			return {
26532
26533				// public
26534				get: function(num)
26535				{
26536					num = (typeof num === 'undefined') ? 1 : num;
26537
26538					var marker = document.createElement('span');
26539
26540					marker.id = 'selection-marker-' + num;
26541					marker.className = 'redactor-selection-marker';
26542					marker.innerHTML = this.opts.invisibleSpace;
26543
26544					return marker;
26545				},
26546				html: function(num)
26547				{
26548					return this.utils.getOuterHtml(this.marker.get(num));
26549				},
26550				find: function(num)
26551				{
26552					num = (typeof num === 'undefined') ? 1 : num;
26553
26554					return this.core.editor().find('span#selection-marker-' + num);
26555				},
26556				insert: function()
26557				{
26558					var sel = this.selection.get();
26559					var range = this.selection.range(sel);
26560
26561					this.marker.insertNode(range, this.marker.get(1), true);
26562					if (range && range.collapsed === false)
26563					{
26564						this.marker.insertNode(range, this.marker.get(2), false);
26565					}
26566
26567				},
26568				remove: function()
26569				{
26570					this.core.editor().find('.redactor-selection-marker').each(this.marker.iterateRemove);
26571				},
26572
26573				// private
26574				insertNode: function(range, node, collapse)
26575				{
26576					var parent = this.selection.parent();
26577					if (range === null || $(parent).closest('.redactor-in').length === 0)
26578					{
26579						return;
26580					}
26581
26582					range = range.cloneRange();
26583
26584					try {
26585						range.collapse(collapse);
26586						range.insertNode(node);
26587					}
26588					catch (e)
26589					{
26590						this.focus.start();
26591					}
26592				},
26593				iterateRemove: function(i, el)
26594				{
26595					var $el = $(el);
26596					var text = $el.text().replace(/\u200B/g, '');
26597					var parent = $el.parent()[0];
26598
26599                    if (text === '') $el.remove();
26600                    else $el.replaceWith(function() { return $(this).contents(); });
26601
26602                    // if (parent && parent.normalize) parent.normalize();
26603				}
26604			};
26605		},
26606
26607		// =modal
26608		modal: function()
26609		{
26610			return {
26611				callbacks: {},
26612				templates: function()
26613				{
26614					this.opts.modal = {
26615						'image-edit': String()
26616						+ '<div class="redactor-modal-tab redactor-group" data-title="General">'
26617							+ '<div id="redactor-image-preview" class="redactor-modal-tab-side">'
26618							+ '</div>'
26619							+ '<div class="redactor-modal-tab-area">'
26620								+ '<section>'
26621									+ '<label>' + this.lang.get('title') + '</label>'
26622									+ '<input type="text" id="redactor-image-title" />'
26623								+ '</section>'
26624								+ '<section>'
26625									+ '<label>' + this.lang.get('caption') + '</label>'
26626									+ '<input type="text" id="redactor-image-caption" aria-label="' + this.lang.get('caption') + '" />'
26627								+ '</section>'
26628								+ '<section>'
26629									+ '<label>' + this.lang.get('link') + '</label>'
26630									+ '<input type="text" id="redactor-image-link" aria-label="' + this.lang.get('link') + '" />'
26631								+ '</section>'
26632								+ '<section>'
26633        							+ '<label class="redactor-image-position-option">' + this.lang.get('image-position') + '</label>'
26634        							+ '<select class="redactor-image-position-option" id="redactor-image-align" aria-label="' + this.lang.get('image-position') + '">'
26635        								+ '<option value="none">' + this.lang.get('none') + '</option>'
26636        								+ '<option value="left">' + this.lang.get('left') + '</option>'
26637        								+ '<option value="center">' + this.lang.get('center') + '</option>'
26638        								+ '<option value="right">' + this.lang.get('right') + '</option>'
26639        							+ '</select>'
26640								+ '</section>'
26641								+ '<section>'
26642									+ '<label class="checkbox"><input type="checkbox" id="redactor-image-link-blank" aria-label="' + this.lang.get('link-in-new-tab') + '"> ' + this.lang.get('link-in-new-tab') + '</label>'
26643								+ '</section>'
26644								+ '<section>'
26645									+ '<button id="redactor-modal-button-action">' + this.lang.get('insert') + '</button>'
26646									+ '<button id="redactor-modal-button-cancel">' + this.lang.get('cancel') + '</button>'
26647									+ '<button id="redactor-modal-button-delete" class="redactor-modal-button-offset">' + this.lang.get('delete') + '</button>'
26648								+ '</section>'
26649							+ '</div>'
26650						+ '</div>',
26651
26652						'image': String()
26653						+ '<div class="redactor-modal-tab" data-title="Upload">'
26654							+ '<section>'
26655								+ '<div id="redactor-modal-image-droparea"></div>'
26656	 						+ '</section>'
26657 						+ '</div>',
26658
26659						'file': String()
26660						+ '<div class="redactor-modal-tab" data-title="Upload">'
26661							+ '<section>'
26662								+ '<label>' + this.lang.get('filename') + ' <span class="desc">(' + this.lang.get('optional') + ')</span></label>'
26663								+ '<input type="text" id="redactor-filename" aria-label="' + this.lang.get('filename') + '" /><br><br>'
26664							+ '</section>'
26665							+ '<section>'
26666								+ '<div id="redactor-modal-file-upload"></div>'
26667							+ '</section>'
26668						+ '</div>',
26669
26670						'link': String()
26671						+ '<div class="redactor-modal-tab" data-title="General">'
26672							+ '<section>'
26673								+ '<label>URL</label>'
26674								+ '<input type="url" id="redactor-link-url" aria-label="URL" />'
26675							+ '</section>'
26676							+ '<section>'
26677								+ '<label>' + this.lang.get('text') + '</label>'
26678								+ '<input type="text" id="redactor-link-url-text" aria-label="' + this.lang.get('text') + '" />'
26679							+ '</section>'
26680              + '<section>'
26681								+ '<label class="checkbox"><input type="checkbox" id="redactor-link-button"> ' + this.lang.get('link-as-button') + '</label>'
26682							+ '</section>'
26683              + '<section>'
26684								+ '<label class="checkbox"><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link-in-new-tab') + '</label>'
26685							+ '</section>'
26686							+ '<section>'
26687								+ '<button id="redactor-modal-button-action">' + this.lang.get('insert') + '</button>'
26688								+ '<button id="redactor-modal-button-cancel">' + this.lang.get('cancel') + '</button>'
26689							+ '</section>'
26690						+ '</div>'
26691					};
26692
26693					$.extend(this.opts, this.opts.modal);
26694
26695				},
26696				addCallback: function(name, callback)
26697				{
26698					this.modal.callbacks[name] = callback;
26699				},
26700				addTemplate: function(name, template)
26701				{
26702					this.opts.modal[name] = template;
26703				},
26704				getTemplate: function(name)
26705				{
26706					return this.opts.modal[name];
26707				},
26708				getModal: function()
26709				{
26710					return this.$modalBody;
26711				},
26712				getActionButton: function()
26713				{
26714					return this.$modalBody.find('#redactor-modal-button-action');
26715				},
26716				getCancelButton: function()
26717				{
26718					return this.$modalBody.find('#redactor-modal-button-cancel');
26719				},
26720				getDeleteButton: function()
26721				{
26722					return this.$modalBody.find('#redactor-modal-button-delete');
26723				},
26724				load: function(templateName, title, width)
26725				{
26726					if (typeof this.$modalBox !== 'undefined' && this.$modalBox.hasClass('open'))
26727					{
26728						return;
26729					}
26730
26731					this.modal.templateName = templateName;
26732					this.modal.width = width;
26733
26734					this.modal.build();
26735					this.modal.enableEvents();
26736					this.modal.setTitle(title);
26737					this.modal.setDraggable();
26738					this.modal.setContent();
26739
26740					// callbacks
26741					if (typeof this.modal.callbacks[templateName] !== 'undefined')
26742					{
26743						this.modal.callbacks[templateName].call(this);
26744					}
26745
26746				},
26747				show: function()
26748				{
26749
26750					if (!this.detect.isDesktop())
26751					{
26752						document.activeElement.blur();
26753					}
26754
26755					this.selection.save();
26756					this.modal.buildTabber();
26757
26758					if (this.detect.isMobile())
26759					{
26760						this.modal.width = '96%';
26761					}
26762
26763					// resize
26764					setTimeout($.proxy(this.modal.buildWidth, this), 0);
26765					$(window).on('resize.redactor-modal', $.proxy(this.modal.buildWidth, this));
26766
26767
26768					this.$modalOverlay.redactorAnimation('fadeIn', {
26769						duration: 0.25
26770					});
26771
26772					this.$modalBox.addClass('open').show();
26773					this.$modal.redactorAnimation('fadeIn', {
26774							timing: 'cubic-bezier(0.175, 0.885, 0.320, 1.105)'
26775						},
26776						$.proxy(function()
26777						{
26778
26779							this.utils.saveScroll();
26780							this.utils.disableBodyScroll();
26781
26782							// modal shown callback
26783							this.core.callback('modalOpened', this.modal.templateName, this.$modal);
26784
26785							// fix bootstrap modal focus
26786							$(document).off('focusin.modal');
26787
26788							// enter
26789							var $elements = this.$modal.find('input[type=text],input[type=url],input[type=email]');
26790							$elements.on('keydown.redactor-modal', $.proxy(this.modal.setEnter, this));
26791
26792						}, this)
26793					);
26794
26795				},
26796				buildWidth: function()
26797				{
26798					var windowHeight = $(window).height();
26799					var windowWidth = $(window).width();
26800
26801					var number = (typeof this.modal.width === 'number');
26802
26803					if (!number && this.modal.width.match(/%$/))
26804					{
26805						this.$modal.css({ 'width': this.modal.width, 'margin-bottom': '16px' });
26806					}
26807					else if (parseInt(this.modal.width) > windowWidth)
26808					{
26809						this.$modal.css({ 'width': '96%', 'margin-bottom': '2%' });
26810					}
26811					else
26812					{
26813						if (number)
26814						{
26815							this.modal.width += 'px';
26816						}
26817
26818						this.$modal.css({ 'width': this.modal.width, 'margin-bottom': '16px' });
26819					}
26820
26821					// margin top
26822					var height = this.$modal.outerHeight();
26823					var top = (windowHeight/2 - height/2) + 'px';
26824
26825					if (this.detect.isMobile())
26826					{
26827						top = '2%';
26828					}
26829					else if (height > windowHeight)
26830					{
26831						top = '16px';
26832
26833					}
26834
26835					this.$modal.css('margin-top', top);
26836				},
26837				buildTabber: function()
26838				{
26839					this.modal.tabs = this.$modal.find('.redactor-modal-tab');
26840
26841					if (this.modal.tabs.length < 2)
26842					{
26843						return;
26844					}
26845
26846					this.modal.$tabsBox = $('<div id="redactor-modal-tabber" />');
26847					$.each(this.modal.tabs, $.proxy(function(i,s)
26848					{
26849						var a = $('<a href="#" rel="' + i + '" />').text($(s).attr('data-title'));
26850
26851						a.on('click', $.proxy(this.modal.showTab, this));
26852
26853						if (i === 0)
26854						{
26855							a.addClass('active');
26856						}
26857
26858						this.modal.$tabsBox.append(a);
26859
26860					}, this));
26861
26862					this.$modalBody.prepend(this.modal.$tabsBox);
26863
26864				},
26865				showTab: function(e)
26866				{
26867					e.preventDefault();
26868
26869					var $el = $(e.target);
26870					var index = $el.attr('rel');
26871
26872					this.modal.tabs.hide();
26873					this.modal.tabs.eq(index).show();
26874
26875					$('#redactor-modal-tabber').find('a').removeClass('active');
26876					$el.addClass('active');
26877
26878					return false;
26879
26880				},
26881				setTitle: function(title)
26882				{
26883					this.$modalHeader.html(title);
26884				},
26885				setContent: function()
26886				{
26887					this.$modalBody.html(this.modal.getTemplate(this.modal.templateName));
26888
26889					this.modal.getCancelButton().on('mousedown', $.proxy(this.modal.close, this));
26890				},
26891				setDraggable: function()
26892				{
26893					if (typeof $.fn.draggable === 'undefined')
26894					{
26895						return;
26896					}
26897
26898					this.$modal.draggable({ handle: this.$modalHeader });
26899					this.$modalHeader.css('cursor', 'move');
26900				},
26901				setEnter: function(e)
26902				{
26903					if (e.which !== 13)
26904					{
26905						return;
26906					}
26907
26908					e.preventDefault();
26909					this.modal.getActionButton().click();
26910				},
26911				build: function()
26912				{
26913					this.modal.buildOverlay();
26914
26915					this.$modalBox = $('<div id="redactor-modal-box"/>').hide();
26916					this.$modal = $('<div id="redactor-modal" role="dialog" />');
26917					this.$modalHeader = $('<div id="redactor-modal-header" />');
26918					this.$modalClose = $('<button type="button" id="redactor-modal-close" aria-label="' + this.lang.get('close') + '" />').html('&times;');
26919					this.$modalBody = $('<div id="redactor-modal-body" />');
26920
26921					this.$modal.append(this.$modalHeader);
26922					this.$modal.append(this.$modalBody);
26923					this.$modal.append(this.$modalClose);
26924					this.$modalBox.append(this.$modal);
26925					this.$modalBox.appendTo(document.body);
26926
26927				},
26928				buildOverlay: function()
26929				{
26930					this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
26931					$('body').prepend(this.$modalOverlay);
26932				},
26933				enableEvents: function()
26934				{
26935					this.$modalClose.on('mousedown.redactor-modal', $.proxy(this.modal.close, this));
26936					$(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
26937					this.core.editor().on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
26938					this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this));
26939				},
26940				disableEvents: function()
26941				{
26942					this.$modalClose.off('mousedown.redactor-modal');
26943					$(document).off('keyup.redactor-modal');
26944					this.core.editor().off('keyup.redactor-modal');
26945					this.$modalBox.off('click§.redactor-modal');
26946					$(window).off('resize.redactor-modal');
26947				},
26948				closeHandler: function(e)
26949				{
26950					if (e.which !== this.keyCode.ESC)
26951					{
26952						return;
26953					}
26954
26955					this.modal.close(false);
26956				},
26957				close: function(e)
26958				{
26959					if (e)
26960					{
26961						if ($(e.target).attr('id') !== 'redactor-modal-button-cancel' && e.target !== this.$modalClose[0] && e.target !== this.$modalBox[0])
26962						{
26963							return;
26964						}
26965
26966						e.preventDefault();
26967					}
26968
26969					if (!this.$modalBox)
26970					{
26971						return;
26972					}
26973
26974					// restore selection
26975					this.selection.restore();
26976
26977					this.modal.disableEvents();
26978					this.utils.enableBodyScroll();
26979					this.utils.restoreScroll();
26980
26981					this.$modalOverlay.redactorAnimation('fadeOut', { duration: 0.4 }, $.proxy(function()
26982					{
26983						this.$modalOverlay.remove();
26984
26985					}, this));
26986
26987					this.$modal.redactorAnimation('fadeOut', {
26988
26989						duration: 0.3,
26990						timing: 'cubic-bezier(0.175, 0.885, 0.320, 1.175)'
26991
26992					}, $.proxy(function()
26993					{
26994						if (typeof this.$modalBox !== 'undefined')
26995						{
26996							this.$modalBox.remove();
26997							this.$modalBox = undefined;
26998						}
26999
27000						this.core.callback('modalClosed', this.modal.templateName);
27001
27002					}, this));
27003
27004				}
27005			};
27006		},
27007
27008		// =observe
27009		observe: function()
27010		{
27011			return {
27012				load: function()
27013				{
27014					if (typeof this.opts.destroyed !== 'undefined')
27015					{
27016						return;
27017					}
27018
27019					this.observe.links();
27020					this.observe.images();
27021
27022				},
27023				isCurrent: function($el, $current)
27024				{
27025					if (typeof $current === 'undefined')
27026					{
27027						$current = $(this.selection.current());
27028					}
27029
27030					return $current.is($el) || $current.parents($el).length > 0;
27031				},
27032				toolbar: function()
27033				{
27034    				this.observe.buttons();
27035	    			this.observe.dropdowns();
27036				},
27037				buttons: function(e, btnName)
27038				{
27039					var current = this.selection.current();
27040					var parent = this.selection.parent();
27041
27042					if (e !== false) this.button.setInactiveAll();
27043					else             this.button.setInactiveAll(btnName);
27044
27045
27046					if (e === false && btnName !== 'html')
27047					{
27048						if ($.inArray(btnName, this.opts.activeButtons) !== -1) this.button.toggleActive(btnName);
27049						return;
27050					}
27051
27052					if (!this.utils.isRedactorParent(current))
27053					{
27054						return;
27055					}
27056
27057					// disable line
27058					if (this.core.editor().css('display') !== 'none')
27059					{
27060	    				if (this.utils.isCurrentOrParentHeader() || this.utils.isCurrentOrParent(['table', 'pre', 'blockquote', 'li'])) this.button.disable('horizontalrule');
27061    					else this.button.enable('horizontalrule');
27062					}
27063
27064					$.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
27065					{
27066						var parentEl = $(parent).closest(key, this.$editor[0]);
27067						var currentEl = $(current).closest(key, this.$editor[0]);
27068
27069						if (parentEl.length !== 0 && !this.utils.isRedactorParent(parentEl))
27070						{
27071							return;
27072						}
27073
27074						if (!this.utils.isRedactorParent(currentEl))
27075						{
27076							return;
27077						}
27078
27079						if (parentEl.length !== 0 || currentEl.closest(key, this.$editor[0]).length !== 0)
27080						{
27081							this.button.setActive(value);
27082						}
27083
27084					}, this));
27085
27086				},
27087				dropdowns: function()
27088				{
27089    				var finded = $('<div />').html(this.selection.html()).find('a').length;
27090					var $current = $(this.selection.current());
27091					var isRedactor = this.utils.isRedactorParent($current);
27092
27093					$.each(this.opts.observe.dropdowns, $.proxy(function(key, value)
27094					{
27095						var observe = value.observe,
27096							element = observe.element,
27097							$item   = value.item,
27098							inValues = typeof observe['in'] !== 'undefined' ? observe['in'] 
27098: false,
27099							outValues = typeof observe.out !== 'undefined' ? observe.out : false;
27100
27101						if (($current.closest(element).length > 0 && isRedactor) || (element === 'a' && finded !== 0))
27102						{
27103							this.observe.setDropdownProperties($item, inValues, outValues);
27104						}
27105						else
27106						{
27107							this.observe.setDropdownProperties($item, outValues, inValues);
27108						}
27109
27110					}, this));
27111				},
27112				setDropdownProperties: function($item, addProperties, deleteProperties)
27113				{
27114					if (deleteProperties && typeof deleteProperties.attr !== 'undefined')
27115					{
27116						this.observe.setDropdownAttr($item, deleteProperties.attr, true);
27117					}
27118
27119					if (typeof addProperties.attr !== 'undefined')
27120					{
27121						this.observe.setDropdownAttr($item, addProperties.attr);
27122					}
27123
27124					if (typeof addProperties.title !== 'undefined')
27125					{
27126						$item.find('span').text(addProperties.title);
27127					}
27128				},
27129				setDropdownAttr: function($item, properties, isDelete)
27130				{
27131					$.each(properties, function(key, value)
27132					{
27133						if (key === 'class')
27134						{
27135							if (!isDelete)
27136							{
27137								$item.addClass(value);
27138							}
27139							else
27140							{
27141								$item.removeClass(value);
27142							}
27143						}
27144						else
27145						{
27146							if (!isDelete)
27147							{
27148								$item.attr(key, value);
27149							}
27150							else
27151							{
27152								$item.removeAttr(key);
27153							}
27154						}
27155					});
27156				},
27157				addDropdown: function($item, btnName, btnObject)
27158				{
27159					if (typeof btnObject.observe === "undefined")
27160					{
27161						return;
27162					}
27163
27164					btnObject.item = $item;
27165
27166					this.opts.observe.dropdowns.push(btnObject);
27167				},
27168				images: function()
27169				{
27170                    if (this.opts.imageEditable)
27171                    {
27172                        this.core.editor().addClass('redactor-layer-img-edit');
27173    					this.core.editor().find('img').each($.proxy(function(i, img)
27174    					{
27175    						var $img = $(img);
27176
27177    						// IE fix (when we clicked on an image and then press backspace IE does goes to image's url)
27178    						$img.closest('a', this.$editor[0]).on('click', function(e) { e.preventDefault(); });
27179
27180    						this.image.setEditable($img);
27181
27182
27183    					}, this));
27184                    }
27185				},
27186				links: function()
27187				{
27188					if (this.opts.linkTooltip)
27189					{
27190    					this.core.editor().find('a').each($.proxy(function(i, s)
27191    					{
27192        					var $link = $(s);
27193        					if ($link.data('cached') !== true)
27194        					{
27195            					$link.data('cached', true);
27196            					$link.on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.showTooltip, this));
27197        					}
27198
27199    					}, this));
27200					}
27201				},
27202				getTooltipPosition: function($link)
27203				{
27204					return $link.offset();
27205				},
27206				showTooltip: function(e)
27207				{
27208					var $el = $(e.target);
27209
27210					if ($el[0].tagName === 'IMG')
27211					{
27212						return;
27213					}
27214
27215					if ($el[0].tagName !== 'A')
27216					{
27217						$el = $el.closest('a', this.$editor[0]);
27218					}
27219
27220					if ($el[0].tagName !== 'A')
27221					{
27222						return;
27223					}
27224
27225					var $link = $el;
27226
27227					var pos = this.observe.getTooltipPosition($link);
27228					var tooltip = $('<span class="redactor-link-tooltip"></span>');
27229
27230					var href = $link.attr('href');
27231					if (href === undefined)
27232					{
27233						href = '';
27234					}
27235
27236					if (href.length > 24)
27237					{
27238						href = href.substring(0, 24) + '...';
27239					}
27240
27241					var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action');
27242					var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action');
27243					var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action');
27244
27245					tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
27246
27247					var lineHeight = parseInt($link.css('line-height'), 10);
27248                    var lineClicked = Math.ceil((e.pageY - pos.top)/lineHeight);
27249                    var top = pos.top + lineClicked * lineHeight;
27250
27251					tooltip.css({
27252						top: top + 'px',
27253						left: pos.left + 'px'
27254					});
27255
27256					$('.redactor-link-tooltip').remove();
27257					$('body').append(tooltip);
27258
27259					this.core.editor().on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
27260					$(document).on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
27261				},
27262				closeAllTooltip: function()
27263				{
27264					$('.redactor-link-tooltip').remove();
27265				},
27266				closeTooltip: function(e)
27267				{
27268					e = e.originalEvent || e;
27269
27270					var target = e.target;
27271					var $parent = $(target).closest('a', this.$editor[0]);
27272					if ($parent.length !== 0 && $parent[0].tagName === 'A' && target.tagName !== 'A')
27273					{
27274						return;
27275					}
27276					else if ((target.tagName === 'A' && this.utils.isRedactorParent(target)) || $(target).hasClass('redactor-link-tooltip-action'))
27277					{
27278						return;
27279					}
27280
27281					this.observe.closeAllTooltip();
27282
27283					this.core.editor().off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
27284					$(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
27285				}
27286
27287			};
27288		},
27289
27290		// =offset
27291		offset: function()
27292		{
27293			return {
27294				get: function(node)
27295				{
27296					var cloned = this.offset.clone(node);
27297					if (cloned === false)
27298					{
27299						return 0;
27300					}
27301
27302					var div = document.createElement('div');
27303					div.appendChild(cloned.cloneContents());
27304					div.innerHTML = div.innerHTML.replace(/<img(.*?[^>])>$/gi, 'i');
27305
27306			        var text = $.trim($(div).text()).replace(/[\t\n\r\n]/g, '').replace(/\u200B/g, '');
27307
27308					return text.length;
27309
27310				},
27311				clone: function(node)
27312				{
27313					var sel = this.selection.get();
27314					var range = this.selection.range(sel);
27315
27316					if (range === null && typeof node === 'undefined')
27317					{
27318						return false;
27319					}
27320
27321					node = (typeof node === 'undefined') ? this.$editor : node;
27322					if (node === false)
27323					{
27324						return false;
27325					}
27326
27327					node = node[0] || node;
27328
27329					var cloned = range.cloneRange();
27330					cloned.selectNodeContents(node);
27331					cloned.setEnd(range.endContainer, range.endOffset);
27332
27333					return cloned;
27334				},
27335				set: function(start, end)
27336				{
27337					end = (typeof end === 'undefined') ? start : end;
27338
27339					if (!this.focus.is())
27340					{
27341						this.focus.start();
27342					}
27343
27344					var sel = this.selection.get();
27345					var range = this.selection.range(sel);
27346					var node, offset = 0;
27347					var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null);
27348
27349					while ((node = walker.nextNode()) !== null)
27350					{
27351						offset += node.nodeValue.length;
27352						if (offset > start)
27353						{
27354							range.setStart(node, node.nodeValue.length + start - offset);
27355							start = Infinity;
27356						}
27357
27358						if (offset >= end)
27359						{
27360							range.setEnd(node, node.nodeValue.length + end - offset);
27361							break;
27362						}
27363					}
27364
27365					range.collapse(false);
27366					this.selection.update(sel, range);
27367				}
27368			};
27369		},
27370
27371		// =paragraphize
27372		paragraphize: function()
27373		{
27374			return {
27375				load: function(html)
27376				{
27377					if (this.opts.paragraphize === false || this.opts.type === 'inline' || this.opts.type === 'pre')
27378					{
27379						return html;
27380					}
27381
27382					if (html === '' || html === '<p></p>')
27383					{
27384						return this.opts.emptyHtml;
27385					}
27386
27387					html = html + "\n";
27388
27389					this.paragraphize.safes = [];
27390					this.paragraphize.z = 0;
27391
27392					// before
27393					html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
27394					html = html.replace(/<\/pre>/gi, "</pre>\n\n");
27395					html = html.replace(/<p>\s<br><\/p>/gi, '<p></p>');
27396
27397					html = this.paragraphize.getSafes(html);
27398
27399					html = html.replace('<br>', "\n");
27400					html = this.paragraphize.convert(html);
27401
27402					html = this.paragraphize.clear(html);
27403					html = this.paragraphize.restoreSafes(html);
27404
27405					// after
27406					html = html.replace(new RegExp('<br\\s?/?>\n?<(' + this.opts.paragraphizeBlocks.join('|') + ')(.*?[^>])>', 'gi'), '<p><br /></p>\n<$1$2>');
27407
27408					return $.trim(html);
27409				},
27410				getSafes: function(html)
27411				{
27412					var $div = $('<div />').append(html);
27413
27414					// remove paragraphs in blockquotes
27415					$div.find('blockquote p').replaceWith(function()
27416					{
27417						return $(this).append('<br />').contents();
27418					});
27419
27420                    $div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s)
27421					{
27422						this.paragraphize.z++;
27423						this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
27424
27425						return $(s).replaceWith("\n#####replace" + this.paragraphize.z + "#####\n\n");
27426
27427
27428					}, this));
27429
27430                    // deal with redactor selection markers
27431                    $div.find('span.redactor-selection-marker').each($.proxy(function(i,s)
27432                    {
27433                        this.paragraphize.z++;
27434                        this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
27435
27436                        return $(s).replaceWith("\n#####replace" + this.paragraphize.z + "#####\n\n");
27437                    }, this));
27438
27439					return $div.html();
27440				},
27441				restoreSafes: function(html)
27442				{
27443					$.each(this.paragraphize.safes, function(i,s)
27444					{
27445						s = (typeof s !== 'undefined') ? s.replace(/\$/g, '&#36;') : s;
27446						html = html.replace('#####replace' + i + '#####', s);
27447
27448					});
27449
27450					return html;
27451				},
27452				convert: function(html)
27453				{
27454					html = html.replace(/\r\n/g, "xparagraphmarkerz");
27455					html = html.replace(/\n/g, "xparagraphmarkerz");
27456					html = html.replace(/\r/g, "xparagraphmarkerz");
27457
27458					var re1 = /\s+/g;
27459					html = html.replace(re1, " ");
27460					html = $.trim(html);
27461
27462					var re2 = /xparagraphmarkerzxparagraphmarkerz/gi;
27463					html = html.replace(re2, "</p><p>");
27464
27465					var re3 = /xparagraphmarkerz/gi;
27466					html = html.replace(re3, "<br>");
27467
27468					html = '<p>' + html + '</p>';
27469
27470					html = html.replace("<p></p>", "");
27471					html = html.replace("\r\n\r\n", "");
27472					html = html.replace(/<\/p><p>/g, "</p>\r\n\r\n<p>");
27473					html = html.replace(new RegExp("<br\\s?/?></p>", "g"), "</p>");
27474					html = html.replace(new RegExp("<p><br\\s?/?>", "g"), "<p>");
27475					html = html.replace(new RegExp("<p><br\\s?/?>", "g"), "<p>");
27476					html = html.replace(new RegExp("<br\\s?/?></p>", "g"), "</p>");
27477					html = html.replace(/<p>&nbsp;<\/p>/gi, "");
27478					html = html.replace(/<p>\s?<br>&nbsp;<\/p>/gi, '');
27479					html = html.replace(/<p>\s?<br>/gi, '<p>');
27480
27481					return html;
27482				},
27483				clear: function(html)
27484				{
27485
27486					html = html.replace(/<p>(.*?)#####replace(.*?)#####\s?<\/p>/gi, '<p>$1</p>#####replace$2#####');
27487					html = html.replace(/(<br\s?\/?>){2,}<\/p>/gi, '</p>');
27488
27489					html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>');
27490					html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>');
27491					html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>');
27492					html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>');
27493
27494					html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
27495					html = html.replace(new RegExp('<p><p>
27495', 'gi'), '<p>');
27496					html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
27497					html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
27498					html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
27499					html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
27500					html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
27501
27502					return html;
27503				}
27504			};
27505		},
27506
27507		// =paste
27508		paste: function()
27509		{
27510			return {
27511				init: function(e)
27512				{
27513					this.rtePaste = true;
27514					var pre = (this.opts.type === 'pre' || this.utils.isCurrentOrParent('pre')) ? true : false;
27515
27516					// clipboard event
27517					if (this.detect.isDesktop())
27518					{
27519
27520    					if (!this.paste.pre && this.opts.clipboardImageUpload && this.opts.imageUpload && this.paste.detectClipboardUpload(e))
27521    					{
27522    						if (this.detect.isIe())
27523    						{
27524    							setTimeout($.proxy(this.paste.clipboardUpload, this), 100);
27525    						}
27526
27527    						return;
27528    					}
27529					}
27530
27531					this.utils.saveScroll();
27532					this.selection.save();
27533					this.paste.createPasteBox(pre);
27534
27535					$(window).on('scroll.redactor-freeze', $.proxy(function()
27536					{
27537						$(window).scrollTop(this.saveBodyScroll);
27538
27539					}, this));
27540
27541					setTimeout($.proxy(function()
27542					{
27543						var html = this.paste.getPasteBoxCode(pre);
27544
27545						// buffer
27546						this.buffer.set();
27547						this.selection.restore();
27548
27549						this.utils.restoreScroll();
27550
27551						// paste info
27552						var data = this.clean.getCurrentType(html);
27553
27554						// clean
27555						html = this.clean.onPaste(html, data);
27556
27557						// callback
27558						var returned = this.core.callback('paste', html);
27559						html = (typeof returned === 'undefined') ? html : returned;
27560
27561						this.paste.insert(html, data);
27562						this.rtePaste = false;
27563
27564						// clean pre breaklines
27565						if (pre)
27566						{
27567							this.clean.cleanPre();
27568						}
27569
27570						$(window).off('scroll.redactor-freeze');
27571
27572					}, this), 1);
27573
27574				},
27575				getPasteBoxCode: function(pre)
27576				{
27577					var html = (pre) ? this.$pasteBox.val() : this.$pasteBox.html();
27578					this.$pasteBox.remove();
27579
27580					return html;
27581				},
27582				createPasteBox: function(pre)
27583				{
27584					var css = { position: 'fixed', width: '1px', top: 0, left: '-9999px' };
27585
27586					this.$pasteBox = (pre) ? $('<textarea>').css(css) : $('<div>').attr('contenteditable', 'true').css(css);
27587					this.paste.appendPasteBox();
27588					this.$pasteBox.focus();
27589				},
27590				appendPasteBox: function()
27591				{
27592					if (this.detect.isIe())
27593					{
27594						this.core.box().append(this.$pasteBox);
27595					}
27596					else
27597					{
27598						// bootstrap modal
27599						var $visibleModals = $('.modal-body:visible');
27600						if ($visibleModals.length > 0)
27601						{
27602							$visibleModals.append(this.$pasteBox);
27603						}
27604						else
27605						{
27606							$('body').prepend(this.$pasteBox);
27607						}
27608					}
27609				},
27610				detectClipboardUpload: function(e)
27611				{
27612					e = e.originalEvent || e;
27613
27614					var clipboard = e.clipboardData;
27615					if (this.detect.isIe() || this.detect.isFirefox())
27616					{
27617						return false;
27618					}
27619
27620					// prevent safari fake url
27621					var types = clipboard.types;
27622					if (types.indexOf('public.tiff') !== -1)
27623					{
27624						e.preventDefault();
27625						return false;
27626					}
27627
27628					if (!clipboard.items || !clipboard.items.length)
27629					{
27630						return;
27631					}
27632
27633					var file = clipboard.items[0].getAsFile();
27634					if (file === null)
27635					{
27636						return false;
27637					}
27638
27639					var reader = new FileReader();
27640					reader.readAsDataURL(file);
27641					reader.onload = $.proxy(this.paste.insertFromClipboard, this);
27642
27643					return true;
27644				},
27645				clipboardUpload: function()
27646				{
27647					var imgs = this.$editor.find('img');
27648					$.each(imgs, $.proxy(function(i,s)
27649					{
27650						if (s.src.search(/^data\:image/i) === -1)
27651						{
27652							return;
27653						}
27654
27655						var formData = !!window.FormData ? new FormData() : null;
27656						if (!window.FormData)
27657						{
27658							return;
27659						}
27660
27661						this.upload.direct = true;
27662						this.upload.type = 'image';
27663						this.upload.url = this.opts.imageUpload;
27664						this.upload.callback = $.proxy(function(data)
27665						{
27666							if (this.detect.isIe())
27667							{
27668								$(s).wrap($('<figure />'));
27669							}
27670
27671							else
27672							{
27673								var $parent = $(s).parent();
27674								this.utils.replaceToTag($parent, 'figure');
27675							}
27676
27677							s.src = data.url;
27678							this.core.callback('imageUpload', $(s), data);
27679
27680						}, this);
27681
27682						var blob = this.utils.dataURItoBlob(s.src);
27683
27684						formData.append('clipboard', 1);
27685						formData.append(this.opts.imageUploadParam, blob);
27686
27687						this.progress.show();
27688						this.upload.send(formData, false);
27689						this.code.sync();
27690						this.rtePaste = false;
27691
27692					}, this));
27693				},
27694				insertFromClipboard: function(e)
27695				{
27696					var formData = !!window.FormData ? new FormData() : null;
27697					if (!window.FormData)
27698					{
27699						return;
27700					}
27701
27702					this.upload.direct = true;
27703					this.upload.type = 'image';
27704					this.upload.url = this.opts.imageUpload;
27705					this.upload.callback = this.image.insert;
27706
27707					var blob = this.utils.dataURItoBlob(e.target.result);
27708
27709					formData.append('clipboard', 1);
27710					formData.append(this.opts.imageUploadParam, blob);
27711
27712					this.progress.show();
27713					this.upload.send(formData, e);
27714					this.rtePaste = false;
27715				},
27716				insert: function(html, data)
27717				{
27718					if (data.pre)
27719					{
27720						this.insert.raw(html);
27721					}
27722					else if (data.text)
27723					{
27724						this.insert.text(html);
27725					}
27726					else
27727					{
27728						this.insert.html(html, data);
27729					}
27730
27731					// Firefox Clipboard Observe
27732					if (this.detect.isFirefox() && this.opts.imageUpload && this.opts.clipboardImageUpload)
27733					{
27734						setTimeout($.proxy(this.paste.clipboardUpload, this), 100);
27735					}
27736
27737				}
27738			};
27739		},
27740
27741		// =placeholder
27742		placeholder: function()
27743		{
27744			return {
27745
27746				// public
27747				enable: function()
27748				{
27749					setTimeout($.proxy(function()
27750					{
27751						return (this.placeholder.isEditorEmpty()) ? this.placeholder.show() : this.placeholder.hide();
27752
27753					}, this), 5);
27754				},
27755				show: function()
27756				{
27757					this.core.editor().addClass('redactor-placeholder');
27758				},
27759				update: function(text)
27760				{
27761					this.opts.placeholder = text;
27762					this.core.editor().attr('placeholder', text);
27763				},
27764				hide: function()
27765				{
27766					this.core.editor().removeClass('redactor-placeholder');
27767				},
27768				is: function()
27769				{
27770					return this.core.editor().hasClass('redactor-placeholder');
27771				},
27772
27773
27774				// private
27775				init: function()
27776				{
27777					if (!this.placeholder.enabled())
27778					{
27779						return;
27780					}
27781
27782					if (!this.utils.isEditorRelative())
27783					{
27784						this.utils.setEditorRelative();
27785					}
27786
27787					this.placeholder.build();
27788					this.placeholder.buildPosition();
27789					this.placeholder.enable();
27790					this.placeholder.enableEvents();
27791
27792				},
27793				enabled: function()
27794				{
27795					return (this.opts.placeholder) ? this.core.element().attr('placeholder', this.opts.placeholder) : this.placeholder.isAttr();
27796				},
27797				enableEvents: function()
27798				{
27799					this.core.editor().on('keydown.redactor-placeholder.' + this.uuid, $.proxy(this.placeholder.enable, this));
27800				},
27801				disableEvents: function()
27802				{
27803					this.core.editor().off('.redactor-placeholder.' + this.uuid);
27804				},
27805				build: function()
27806				{
27807					this.core.editor().attr('placeholder', this.core.element().attr('placeholder'));
27808				},
27809				buildPosition: function()
27810				{
27811					var $style = $('<style />');
27812					$style.addClass('redactor-placeholder-style-tag');
27813					$style.html('#' + this.core.id() + '.redactor-placeholder::after ' + this.placeholder.getPosition());
27814
27815					$('head').append($style);
27816				},
27817				getPosition: function()
27818				{
27819					return '{ top: ' + this.core.editor().css('padding-top') + '; left: ' + this.core.editor().css('padding-left') + '; }';
27820				},
27821				isEditorEmpty: function()
27822				{
27823					var html = $.trim(this.core.editor().html()).replace(/[\t\n]/g, '');
27824					var states = ['', '<p>​</p>', '<p>​<br></p>', this.opts.emptyHtmlRendered];
27825
27826					return ($.inArray(html, states) !== -1);
27827				},
27828				isAttr: function()
27829				{
27830					return (typeof this.core.element().attr('placeholder') !== 'undefined' && this.core.element().attr('placeholder') !== '');
27831				},
27832				destroy: function()
27833				{
27834					this.core.editor().removeAttr('placeholder');
27835
27836					this.placeholder.hide();
27837					this.placeholder.disableEvents();
27838
27839					$('.redactor-placeholder-style-tag').remove();
27840				}
27841			};
27842		},
27843
27844		// =progress
27845		progress: function()
27846		{
27847			return {
27848				$box: null,
27849				$bar: null,
27850				target: document.body,  // or id selector
27851
27852				// public
27853				show: function()
27854				{
27855					if (!this.progress.is())
27856					{
27857						this.progress.build();
27858						this.progress.$box.redactorAnimation('fadeIn');
27859					}
27860					else
27861					{
27862						this.progress.$box.show();
27863					}
27864				},
27865				hide: function()
27866				{
27867					if (this.progress.is())
27868					{
27869						this.progress.$box.redactorAnimation('fadeOut', { duration: 0.35 }, $.proxy(this.progress.destroy, this));
27870					}
27871				},
27872				update: function(value)
27873				{
27874					this.progress.show();
27875					this.progress.$bar.css('width', value + '%');
27876				},
27877				is: function()
27878				{
27879					return (this.progress.$box === null) ? false : true;
27880				},
27881
27882				// private
27883				build: function()
27884				{
27885					this.progress.$bar = $('<span />');
27886					this.progress.$box = $('<div id="redactor-progress" />');
27887
27888					this.progress.$box.append(this.progress.$bar);
27889					$(this.progress.target).append(this.progress.$box);
27890				},
27891				destroy: function()
27892				{
27893					if (this.progress.is())
27894					{
27895						this.progress.$box.remove();
27896					}
27897
27898					this.progress.$box = null;
27899					this.progress.$bar = null;
27900				}
27901			};
27902		},
27903
27904		// =selection
27905		selection: function()
27906		{
27907			return {
27908				get: function()
27909				{
27910					if (window.getSelection)
27911					{
27912						return window.getSelection();
27913					}
27914					else if (document.selection && document.selection.type !== "Control")
27915					{
27916						return document.selection;
27917					}
27918
27919					return null;
27920				},
27921				range: function(sel)
27922				{
27923					if (typeof sel === 'undefined')
27924					{
27925						sel = this.selection.get();
27926					}
27927
27928					if (sel.getRangeAt && sel.rangeCount)
27929					{
27930						return sel.getRangeAt(0);
27931					}
27932
27933					return null;
27934				},
27935				is: function()
27936				{
27937					return (this.selection.isCollapsed()) ? false : true;
27938				},
27939				isRedactor: function()
27940				{
27941					var range = this.selection.range();
27942
27943					if (range !== null)
27944					{
27945						var el = range.startContainer.parentNode;
27946
27947						if ($(el).hasClass('redactor-in') || $(el).parents('.redactor-in').length !== 0)
27948						{
27949							return true;
27950						}
27951					}
27952
27953					return false;
27954				},
27955				isCollapsed: function()
27956				{
27957					var sel = this.selection.get();
27958
27959					return (sel === null) ? false : sel.isCollapsed;
27960				},
27961				update: function(sel, range)
27962				{
27963					if (range === null)
27964					{
27965						return;
27966					}
27967
27968					sel.removeAllRanges();
27969					sel.addRange(range);
27970				},
27971				current: function()
27972				{
27973					var sel = this.selection.get();
27974
27975					return (sel === null) ? false : sel.anchorNode;
27976				},
27977				parent: function()
27978				{
27979					var current = this.selection.current();
27980
27981					return (current === null) ? false : current.parentNode;
27982				},
27983				block: function(node)
27984				{
27985					node = node || this.selection.current();
27986
27987					while (node)
27988					{
27989						if (this.utils.isBlockTag(node.tagName))
27990						{
27991							return ($(node).hasClass('redactor-in')) ? false : node;
27992						}
27993
27994						node = node.parentNode;
27995					}
27996
27997					return false;
27998				},
27999				inline: function(node)
28000				{
28001					node = node || this.selection.current();
28002
28003					while (node)
28004					{
28005						if (this.utils.isInlineTag(node.tagName))
28006						{
28007							return ($(node).hasClass('redactor-in')) ? false : node;
28008						}
28009
28010						node = node.parentNode;
28011					}
28012
28013					return false;
28014				},
28015				element: function(node)
28016				{
28017					if (!node)
28018					{
28019						node = this.selection.current();
28020					}
28021
28022					while (node)
28023					{
28024						if (node.nodeType === 1)
28025						{
28026							if ($(node).hasClass('redactor-in'))
28027							{
28028								return false;
28029							}
28030
28031							return node;
28032						}
28033
28034						node = node.parentNode;
28035					}
28036
28037					return false;
28038				},
28039				prev: function()
28040				{
28041					var current = this.selection.current();
28042
28043					return (current === null) ? false : this.selection.current().previousSibling;
28044				},
28045				next: function()
28046				{
28047					var current = this.selection.current();
28048
28049					return (current === null) ? false : this.selection.current().nextSibling;
28050				},
28051				blocks: function(tag)
28052				{
28053					var blocks = [];
28054					var nodes = this.selection.nodes(tag);
28055
28056					$.each(nodes, $.proxy(function(i,node)
28057					{
28058						if (this.utils.isBlock(node))
28059						{
28060							blocks.push(node);
28061						}
28062
28063					}, this));
28064
28065					var block = this.selection.block();
28066					if (blocks.length === 0 && block === false)
28067					{
28068						return [];
28069					}
28070					else if (blocks.length === 0 && block !== false)
28071					{
28072						return [block];
28073					}
28074					else
28075					{
28076						return blocks;
28077					}
28078
28079				},
28080				inlines: function(tag)
28081				{
28082					var inlines = [];
28083					var nodes = this.selection.nodes(tag);
28084
28085					$.each(nodes, $.proxy(function(i,node)
28086					{
28087						if (this.utils.isInline(node))
28088						{
28089							inlines.push(node);
28090						}
28091
28092					}, this));
28093
28094					var inline = this.selection.inline();
28095					if (inlines.length === 0 && inline === false)
28096					{
28097						return [];
28098					}
28099					else if (inlines.length === 0 && inline !== false)
28100					{
28101						return [inline];
28102					}
28103					else
28104					{
28105						return inlines;
28106					}
28107				},
28108				nodes: function(tag)
28109				{
28110                    var filter = (typeof tag === 'undefined') ? [] : (($.isArray(tag)) ? tag : [tag]);
28111
28112					var sel = this.selection.get();
28113					var range = this.selection.range(sel);
28114                    var nodes = [];
28115                    var resultNodes = [];
28116
28117					if (this.utils.isCollapsed())
28118					{
28119						nodes = [this.selection.current()];
28120					}
28121					else
28122					{
28123						var node = range.startContainer;
28124						var endNode = range.endContainer;
28125
28126						// single node
28127						if (node === endNode)
28128						{
28129							return [node];
28130						}
28131
28132						// iterate
28133						while (node && node !== endNode)
28134						{
28135							nodes.push(node = this.selection.nextNode(node));
28136						}
28137
28138						// partially selected nodes
28139						node = range.startContainer;
28140						while (node && node !== range.commonAncestorContainer)
28141						{
28142							nodes.unshift(node);
28143							node = node.parentNode;
28144						}
28145					}
28146
28147					// remove service nodes
28148					$.each(nodes, function(i,s)
28149					{
28150						if (s)
28151						{
28152							var tagName = (s.nodeType !== 1) ? false : s.tagName.toLowerCase();
28153
28154							if ($(s).hasClass('redactor-script-tag') || $(s).hasClass('redactor-selection-marker'))
28155							{
28156								return;
28157							}
28158							else if (tagName && filter.length !== 0 && $.inArray(tagName, filter) === -1)
28159							{
28160								return;
28161							}
28162							else
28163							{
28164								resultNodes.push(s);
28165							}
28166						}
28167					});
28168
28169					return (resultNodes.length === 0) ? [] : resultNodes;
28170				},
28171				nextNode: function(node)
28172				{
28173					if (node.hasChildNodes())
28174					{
28175						return node.firstChild;
28176					}
28177					else
28178					{
28179						while (node && !node.nextSibling)
28180						{
28181							node = node.parentNode;
28182						}
28183
28184						if (!node)
28185						{
28186							return null;
28187						}
28188
28189						return node.nextSibling;
28190					}
28191				},
28192				save: function()
28193				{
28194 					this.marker.insert();
28195					this.savedSel = this.core.editor().html();
28196				},
28197				restore: function(removeMarkers)
28198				{
28199                    var node1 = this.marker.find(1);
28200					var node2 = this.marker.find(2);
28201
28202					if (this.detect.isFirefox())
28203					{
28204						this.core.editor().focus();
28205					}
28206
28207					if (node1.length !== 0 && node2.length !== 0)
28208					{
28209						this.caret.set(node1, node2);
28210					}
28211					else if (node1.length !== 0)
28212					{
28213						this.caret.start(node1);
28214					}
28215					else
28216					{
28217						this.core.editor().focus();
28218					}
28219
28220					if (removeMarkers !== false)
28221					{
28222						this.marker.remove();
28223						this.savedSel = false;
28224					}
28225				},
28226                saveInstant: function()
28227                {
28228                    var el = this.core.editor()[0];
28229                    var doc = el.ownerDocument, win = doc.defaultView;
28230                    var sel = win.getSelection();
28231
28232                    if (!sel.getRangeAt || !sel.rangeCount)
28233                    {
28234                        return;
28235                    }
28236
28237                    var range = sel.getRangeAt(0);
28238                    var selectionRange = range.cloneRange();
28239
28240                    selectionRange.selectNodeContents(el);
28241                    selectionRange.setEnd(range.startContainer, range.startOffset);
28242
28243                    var start = selectionRange.toString().length;
28244
28245                    this.saved = {
28246                        start: start,
28247                        end: start + range.toString().length,
28248                        node: range.startContainer
28249                    };
28250
28251                    return this.saved;
28252                },
28253                restoreInstant: function(saved)
28254                {
28255                    if (typeof saved === 'undefined' && !this.saved)
28256                    {
28257                        return;
28258                    }
28259
28260                    this.saved = (typeof saved !== 'undefined') ? saved : this.saved;
28261
28262                    var $node = this.core.editor().find(this.saved.node);
28263                    if ($node.length !== 0 && $node.text().trim().replace(/\u200B/g, '').length === 0)
28264                    {
28265                       try {
28266                            var range = document.createRange();
28267                            range.setStart($node[0], 0);
28268
28269                            var sel = window.getSelection();
28270                            sel.removeAllRanges();
28271                            sel.addRange(range);
28272                        }
28273                        catch(e) {}
28274
28275                        return;
28276                    }
28277
28278                    var el = this.core.editor()[0];
28279                    var doc = el.ownerDocument, win = doc.defaultView;
28280                    var charIndex = 0, range = doc.createRange();
28281
28282                    range.setStart(el, 0);
28283                    range.collapse(true);
28284
28285                    var nodeStack = [el], node, foundStart = false, stop = false;
28286                    while (!stop && (node = nodeStack.pop()))
28287                    {
28288                        if (node.nodeType == 3)
28289                        {
28290                            var nextCharIndex = charIndex + node.length;
28291                            if (!foundStart && this.saved.start >= charIndex && this.saved.start <= nextCharIndex)
28292                            {
28293                                range.setStart(node, this.saved.start - charIndex);
28294                                foundStart = true;
28295                            }
28296
28297                            if (foundStart && this.saved.end >= charIndex && this.saved.end <= nextCharIndex)
28298                            {
28299                                range.setEnd(node, this.saved.end - charIndex);
28300                                stop = true;
28301                            }
28302                            charIndex = nextCharIndex;
28303                        }
28304                        else
28305                        {
28306                            var i = node.childNodes.length;
28307                            while (i--)
28308                            {
28309                                nodeStack.push(node.childNodes[i]);
28310                            }
28311                        }
28312                    }
28313
28314                    var sel = win.getSelection();
28315                    sel.removeAllRanges();
28316                    sel.addRange(range);
28317                },
28318				node: function(node)
28319				{
28320					$(node).prepend(this.marker.get(1));
28321					$(node).append(this.marker.get(2));
28322
28323					this.selection.restore();
28324				},
28325				all: function()
28326				{
28327					this.core.editor().focus();
28328
28329					var sel = this.selection.get();
28330					var range = this.selection.range(sel);
28331
28332					range.selectNodeContents(this.core.editor()[0]);
28333
28334					this.selection.update(sel, range);
28335				},
28336				remove: function()
28337				{
28338					this.selection.get().removeAllRanges();
28339				},
28340				replace: function(html)
28341				{
28342					this.insert.html(html);
28343				},
28344				text: function()
28345				{
28346					return this.selection.get().toString();
28347				},
28348				html: function()
28349				{
28350					var html = '';
28351					var sel = this.selection.get();
28352
28353					if (sel.rangeCount)
28354					{
28355						var container = document.createElement('div');
28356						var len = sel.rangeCount;
28357						for (var i = 0; i < len; ++i)
28358						{
28359							container.appendChild(sel.getRangeAt(i).cloneContents());
28360						}
28361
28362						html = this.clean.onGet(container.innerHTML);
28363					}
28364
28365					return html;
28366				},
28367				extractEndOfNode: function(node)
28368				{
28369					var sel = this.selection.get();
28370					var range = this.selection.range(sel);
28371
28372			        var clonedRange = range.cloneRange();
28373			        clonedRange.selectNodeContents(node);
28374			        clonedRange.setStart(range.endContainer, range.endOffset);
28375
28376			        return clonedRange.extractContents();
28377				},
28378
28379				// #backward
28380				removeMarkers: function()
28381				{
28382					this.marker.remove();
28383				},
28384				marker: function(num)
28385				{
28386					return this.marker.get(num);
28387				},
28388				markerHtml: function(num)
28389				{
28390					return this.marker.html(num);
28391				}
28392
28393			};
28394		},
28395
28396		// =shortcuts
28397		shortcuts: function()
28398		{
28399			return {
28400				// based on https://github.com/jeresig/jquery.hotkeys
28401				hotkeysSpecialKeys: {
28402					8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
28403					20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
28404					37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
28405					96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
28406					104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
28407					112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
28408					120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
28409					188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
28410				},
28411				hotkeysShiftNums: {
28412					"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
28413					"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
28414					".": ">",  "/": "?",  "\\": "|"
28415				},
28416				init: function(e, key)
28417				{
28418					// disable browser's hot keys for bold and italic if shortcuts off
28419					if (this.opts.shortcuts === false)
28420					{
28421						if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73))
28422						{
28423							e.preventDefault();
28424						}
28425
28426						return false;
28427					}
28428					else
28429					{
28430						// build
28431						$.each(this.opts.shortcuts, $.proxy(function(str, command)
28432						{
28433							this.shortcuts.build(e, str, command);
28434
28435						}, this));
28436					}
28437				},
28438				build: function(e, str, command)
28439				{
28440					var handler = $.proxy(function()
28441					{
28442						this.shortcuts.buildHandler(command);
28443
28444					}, this);
28445
28446					var keys = str.split(',');
28447					var len = keys.length;
28448					for (var i = 0; i < len; i++)
28449					{
28450						if (typeof keys[i] === 'string')
28451						{
28452							this.shortcuts.handler(e, $.trim(keys[i]), handler);
28453						}
28454					}
28455
28456				},
28457				buildHandler: function(command)
28458				{
28459					var func;
28460					if (command.func.search(/\./) !== '-1')
28461					{
28462						func = command.func.split('.');
28463						if (typeof this[func[0]] !== 'undefined')
28464						{
28465							this[func[0]][func[1]].apply(this, command.params);
28466						}
28467					}
28468					else
28469					{
28470						this[command.func].apply(this, command.params);
28471					}
28472				},
28473				handler: function(e, keys, origHandler)
28474				{
28475					keys = keys.toLowerCase().split(" ");
28476
28477					var special = this.shortcuts.hotkeysSpecialKeys[e.keyCode];
28478					var character = String.fromCharCode(e.which).toLowerCase();
28479					var modif = "", possible = {};
28480
28481					$.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
28482					{
28483						if (e[specialKey + 'Key'] && special !== specialKey)
28484						{
28485							modif += specialKey + '+';
28486						}
28487					});
28488
28489
28490					if (special)
28491					{
28492						possible[modif + special] = true;
28493					}
28494
28495					if (character)
28496					{
28497						possible[modif + character] = true;
28498						possible[modif + this.shortcuts.hotkeysShiftNums[character]] = true;
28499
28500						// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
28501						if (modif === "shift+")
28502						{
28503							possible[this.shortcuts.hotkeysShiftNums[character]] = true;
28504						}
28505					}
28506
28507					var len = keys.length;
28508					for (var i = 0; i < len; i++)
28509					{
28510						if (possible[keys[i]])
28511						{
28512							e.preventDefault();
28513							return origHandler.apply(this, arguments);
28514						}
28515					}
28516				}
28517			};
28518		},
28519
28520		// =storage
28521		storage: function()
28522		{
28523			return {
28524				data: [],
28525				add: function(data)
28526				{
28527					// type, node, url, id
28528					data.status = true;
28529					data.url = decodeURI(data.url);
28530
28531					this.storage.data[data.url] = data;
28532				},
28533				status: function(url, status)
28534				{
28535					this.storage.data[decodeURI(url)].status = status;
28536				},
28537				observe: function()
28538				{
28539					var _this = this;
28540
28541					var $images = this.core.editor().find('[data-image]');
28542					$images.each(function(i, s)
28543					{
28544						_this.storage.add({ type: 'image', node: s, url: s.src, id: $(s).attr('data-image') });
28545					});
28546
28547					var $files = this.core.editor().find('[data-file]');
28548					$files.each(function(i, s)
28549					{
28550						_this.storage.add({ type: 'file', node: s, url: s.href, id: $(s).attr('data-file') });
28551					});
28552
28553					var $s3 = this.core.editor().find('[data-s3]');
28554					$s3.each(function(i, s)
28555					{
28556						var url = (s.tagName === 'IMG') ? s.src : s.href;
28557						_this.storage.add({ type: 's3', node: s, url: url, id: $(s).attr('data-s3') });
28558					});
28559
28560				},
28561				changes: function()
28562				{
28563					for (var key in this.storage.data)
28564					{
28565						var data = this.storage.data[key];
28566						var attr = (data.node.tagName === 'IMG') ? 'src' : 'href';
28567						var $el = this.core.editor().find('[data-' + data.type + '][' + attr + '="' + data.url + '"]');
28568
28569						if ($el.length === 0)
28570						{
28571							this.storage.status(data.url, false);
28572						}
28573						else
28574						{
28575							this.storage.status(data.url, true);
28576						}
28577					}
28578
28579
28580					return this.storage.data;
28581				}
28582
28583			};
28584		},
28585
28586		// =toolbar
28587		toolbar: function()
28588		{
28589			return {
28590				build: function()
28591				{
28592					this.button.hideButtons();
28593					this.button.hideButtonsOnMobile();
28594
28595                    this.$toolbarBox = $('<div />').addClass('redactor-toolbar-box');
28596					this.$toolbar = this.toolbar.createContainer();
28597					this.$toolbarBox.append(this.$toolbar);
28598
28599					this.toolbar.append();
28600					this.button.$toolbar = this.$toolbar;
28601					this.button.setFormatting();
28602					this.button.load(this.$toolbar);
28603
28604					this.toolbar.setOverflow();
28605					this.toolbar.setFixed();
28606
28607				},
28608				createContainer: function()
28609				{
28610					return $('<ul>').addClass('redactor-toolbar').attr({ 'id': 'redactor-toolbar-' + this.uuid, 'role': 'toolbar' });
28611				},
28612				append: function()
28613				{
28614					if (this.opts.toolbarExternal)
28615					{
28616						this.$toolbar.addClass('redactor-toolbar-external');
28617						$(this.opts.toolbarExternal).html(this.$toolbarBox);
28618					}
28619					else
28620					{
28621						if (this.opts.type === 'textarea')
28622						{
28623							this.$box.prepend(this.$toolbarBox);
28624						}
28625						else
28626						{
28627							this.$element.before(this.$toolbarBox);
28628						}
28629
28630					}
28631				},
28632				setOverflow: function()
28633				{
28634    				if (this.opts.toolbarOverflow)
28635					{
28636    					this.$toolbar.addClass('redactor-toolbar-overflow');
28637    				}
28638				},
28639				setFixed: function()
28640				{
28641					if (!this.opts.toolbarFixed || this.opts.toolbarExternal)
28642					{
28643						return;
28644					}
28645
28646					if (this.opts.toolbarFixedTarget !== document)
28647					{
28648						var $el = $(this.opts.toolbarFixedTarget);
28649						this.toolbar.toolbarOffsetTop = ($el.length === 0) ? 0 : this.core.box().offset().top - $el.offset().top;
28650					}
28651
28652					// bootstrap modal fix
28653					var late = (this.core.box().closest('.modal-body').length !== 0) ? 1000 : 0;
28654
28655					setTimeout($.proxy(function()
28656					{
28657    					var self = this;
28658						this.toolbar.observeScroll(false);
28659						if (this.detect.isDesktop())
28660						{
28661							$(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, function()
28662							{
28663                                if (self.core.editor().height() < 100 || self.placeholder.isEditorEmpty())
28664            					{
28665                					return;
28666            					}
28667
28668    							self.toolbar.observeScroll();
28669                            });
28670						}
28671						else
28672						{
28673							$(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, function()
28674							{
28675                                if (self.core.editor().height() < 100 || self.placeholder.isEditorEmpty())
28676            					{
28677                					return;
28678            					}
28679
28680								self.core.toolbar().hide();
28681								clearTimeout($.data(this, "scrollCheck"));
28682								$.data(this, "scrollCheck", setTimeout(function()
28683								{
28684									self.core.toolbar().show();
28685									self.toolbar.observeScroll();
28686								}, 250) );
28687
28688							});
28689						}
28690
28691					}, this), late);
28692
28693				},
28694				setUnfixed: function()
28695				{
28696    				this.toolbar.observeScrollDisable();
28697				},
28698				getBoxTop: function()
28699				{
28700					return (this.opts.toolbarFixedTarget === document) ? this.core.box().offset().top : this.toolbar.toolbarOffsetTop;
28701				},
28702				observeScroll: function(start)
28703				{
28704					// tolerance 0 if redactor in the hidden layer
28705					var tolerance = 0;
28706
28707					if (start !== false)
28708					{
28709						tolerance = (this.opts.toolbarFixedTarget === document) ? 20 : 0;
28710					}
28711
28712					var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
28713					var boxTop = this.toolbar.getBoxTop();
28714
28715                    if (scrollTop === boxTop)
28716                    {
28717                        return;
28718                    }
28719
28720					if ((scrollTop + this.opts.toolbarFixedTopOffset + tolerance) > boxTop)
28721					{
28722						this.toolbar.observeScrollEnable(scrollTop, boxTop);
28723					}
28724					else
28725					{
28726						this.toolbar.observeScrollDisable();
28727					}
28728				},
28729				observeScrollResize: function()
28730				{
28731					this.$toolbar.css({
28732
28733						width: this.core.box().innerWidth(),
28734						left: this.core.box().offset().left
28735
28736					});
28737				},
28738				observeScrollEnable: function(scrollTop, boxTop)
28739				{
28740					if (typeof this.fullscreen !== 'undefined' && this.fullscreen.isOpened === false)
28741					{
28742						this.toolbar.observeScrollDisable();
28743						return;
28744					}
28745
28746					var end = boxTop + this.core.box().outerHeight() - 32;
28747					var width = this.core.box().innerWidth();
28748
28749					var position = (this.detect.isDesktop()) ? 'fixed' : 'absolute';
28750					var top = (this.detect.isDesktop()) ? this.opts.toolbarFixedTopOffset : ($(this.opts.toolbarFixedTarget).scrollTop() - boxTop + this.opts.toolbarFixedTopOffset);
28751					var left = (this.detect.isDesktop()) ? this.core.box().offset().left : 0;
28752
28753
28754					if (this.opts.toolbarFixedTarget !== document)
28755					{
28756						 position = 'absolute';
28757						 top = this.opts.toolbarFixedTopOffset + $(this.opts.toolbarFixedTarget).scrollTop() - boxTop;
28758						 left = 0;
28759					}
28760
28761					this.$toolbar.addClass('toolbar-fixed-box');
28762
28763					this.$toolbar.css({
28764						position: position,
28765						width: width,
28766						top: top,
28767						left: left
28768					});
28769
28770
28771					if (scrollTop > end)
28772					{
28773						$('.redactor-dropdown-' + this.uuid + ':visible').hide();
28774					}
28775
28776					this.toolbar.setDropdownsFixed();
28777
28778					this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
28779					$(window).on('resize.redactor-toolbar.' + this.uuid, $.proxy(this.toolbar.observeScrollResize, this));
28780				},
28781				observeScrollDisable: function()
28782				{
28783					this.$toolbar.css({
28784						position: 'relative',
28785						width: 'auto',
28786						top: 0,
28787						left: 0,
28788						visibility: 'visible'
28789					});
28790
28791					this.toolbar.unsetDropdownsFixed();
28792					this.$toolbar.removeClass('toolbar-fixed-box');
28793					$(window).off('resize.redactor-toolbar.' + this.uuid);
28794				},
28795				setDropdownsFixed: function()
28796				{
28797					var position = (this.opts.toolbarFixedTarget === document && this.detect.isDesktop()) ? 'fixed' : 'absolute';
28798					this.toolbar.setDropdownPosition(position);
28799				},
28800				unsetDropdownsFixed: function()
28801				{
28802					this.toolbar.setDropdownPosition('absolute');
28803				},
28804				setDropdownPosition: function(position)
28805				{
28806					var self = this;
28807					$('.redactor-dropdown-' + this.uuid).each(function()
28808					{
28809						var $el = $(this);
28810						var $button = self.button.get($el.attr('rel'));
28811						var top = (position === 'fixed') ? self.opts.toolbarFixedTopOffset : $button.offset().top;
28812
28813						$el.css({ position: position, top: ($button.innerHeight() + top) + 'px' });
28814					});
28815				}
28816			};
28817		},
28818
28819		// =upload
28820		upload: function()
28821		{
28822			return {
28823				init: function(id, url, callback)
28824				{
28825					this.upload.direct = false;
28826					this.upload.callback = callback;
28827					this.upload.url = url;
28828					this.upload.$el = $(id);
28829					this.upload.$droparea = $('<div id="redactor-droparea" />');
28830
28831					this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text(this.lang.get('upload-label'));
28832					this.upload.$input = $('<input type="file" name="file" multiple />');
28833
28834					this.upload.$placeholdler.append(this.upload.$input);
28835					this.upload.$droparea.append(this.upload.$placeholdler);
28836					this.upload.$el.append(this.upload.$droparea);
28837
28838					this.upload.$droparea.off('redactor.upload');
28839					this.upload.$input.off('redactor.upload');
28840
28841					this.upload.$droparea.on('dragover.redactor.upload', $.proxy(this.upload.onDrag, this));
28842					this.upload.$droparea.on('dragleave.redactor.upload', $.proxy(this.upload.onDragLeave, this));
28843
28844					// change
28845					this.upload.$input.on('change.redactor.upload', $.proxy(function(e)
28846					{
28847                        e = e.originalEvent || e;
28848                        var len = this.upload.$input[0].files.length;
28849
28850                        for (var i = 0; i < len; i++)
28851                        {
28852                            var index = (len - 1) - i;
28853                            this.upload.traverseFile(this.upload.$input[0].files[index], e);
28854                        }
28855					}, this));
28856
28857					// drop
28858					this.upload.$droparea.on('drop.redactor.upload', $.proxy(function(e)
28859					{
28860						e.preventDefault();
28861
28862						this.upload.$droparea.removeClass('drag-hover').addClass('drag-drop');
28863						this.upload.onDrop(e);
28864
28865					}, this));
28866				},
28867				directUpload: function(file, e)
28868				{
28869					this.upload.direct = true;
28870					this.upload.traverseFile(file, e);
28871				},
28872				onDrop: function(e)
28873				{
28874					e = e.originalEvent || e;
28875					var files = e.dataTransfer.files;
28876
28877					if (this.opts.multipleImageUpload)
28878					{
28879						var len = files.length;
28880						for (var i = 0; i < len; i++)
28881						{
28882							this.upload.traverseFile(files[i], e);
28883						}
28884					}
28885					else
28886					{
28887						this.upload.traverseFile(files[0], e);
28888					}
28889				},
28890				traverseFile: function(file, e)
28891				{
28892					if (this.opts.s3)
28893					{
28894						this.upload.setConfig(file);
28895						this.uploads3.send(file, e);
28896						return;
28897					}
28898
28899					var formData = !!window.FormData ? new FormData() : null;
28900					if (window.FormData)
28901					{
28902						this.upload.setConfig(file);
28903
28904						var name = (this.upload.type === 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam;
28905						formData.append(name, file);
28906					}
28907
28908					var stop = this.core.callback('uploadStart', e, formData);
28909					if (stop !== false)
28910					{
28911    					this.progress.show();
28912				    	this.upload.send(formData, e);
28913					}
28914				},
28915				setConfig: function(file)
28916				{
28917					this.upload.getType(file);
28918
28919					if (this.upload.direct)
28920					{
28921						this.upload.url = (this.upload.type === 'image') ? this.opts.imageUpload : this.opts.fileUpload;
28922						this.upload.callback = (this.upload.type === 'image') ? this.image.insert : this.file.insert;
28923					}
28924				},
28925				getType: function(file)
28926				{
28927					this.upload.type = (this.opts.imageTypes.indexOf(file.type) === -1) ? 'file' : 'image';
28928
28929					if (this.opts.imageUpload === null && this.opts.fileUpload !== null)
28930					{
28931						this.upload.type = 'file';
28932					}
28933				},
28934				getHiddenFields: function(obj, fd)
28935				{
28936					if (obj === false || typeof obj !== 'object')
28937					{
28938						return fd;
28939					}
28940
28941					$.each(obj, $.proxy(function(k, v)
28942					{
28943						if (v !== null && v.toString().indexOf('#') === 0)
28944						{
28945							v = $(v).val();
28946						}
28947
28948						fd.append(k, v);
28949
28950					}, this));
28951
28952					return fd;
28953
28954				},
28955				send: function(formData, e)
28956				{
28957					// append hidden fields
28958					if (this.upload.type === 'image')
28959					{
28960						formData = this.utils.appendFields(this.opts.imageUploadFields, formData);
28961						formData = this.utils.appendForms(this.opts.imageUploadForms, formData);
28962						formData = this.upload.getHiddenFields(this.upload.imageFields, formData);
28963					}
28964					else
28965					{
28966						formData = this.utils.appendFields(this.opts.fileUploadFields, formData);
28967						formData = this.utils.appendForms(this.opts.fileUploadForms, formData);
28968						formData = this.upload.getHiddenFields(this.upload.fileFields, formData);
28969					}
28970
28971					var xhr = new XMLHttpRequest();
28972					xhr.open('POST', this.upload.url);
28973					xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
28974
28975					// complete
28976					xhr.onreadystatechange = $.proxy(function()
28977					{
28978					    if (xhr.readyState === 4)
28979					    {
28980					        var data = xhr.responseText;
28981
28982							data = data.replace(/^\[/, '');
28983							data = data.replace(/\]$/, '');
28984
28985							var json;
28986							try
28987							{
28988								json = (typeof data === 'string' ? JSON.parse(data) : data);
28989							}
28990							catch(err)
28991							{
28992								json = { error: true };
28993							}
28994
28995
28996							this.progress.hide();
28997
28998							if (!this.upload.direct)
28999							{
29000								this.upload.$droparea.removeClass('drag-drop');
29001							}
29002
29003							this.upload.callback(json, this.upload.direct, e);
29004					    }
29005					}, this);
29006
29007                    // before send
29008                    var stop = this.core.callback('uploadBeforeSend', xhr);
29009                    if (stop !== false)
29010                    {
29011                        xhr.send(formData);
29012					}
29013				},
29014				onDrag: function(e)
29015				{
29016					e.preventDefault();
29017					this.upload.$droparea.addClass('drag-hover');
29018				},
29019				onDragLeave: function(e)
29020				{
29021					e.preventDefault();
29022					this.upload.$droparea.removeClass('drag-hover');
29023				},
29024				clearImageFields: function()
29025				{
29026					this.upload.imageFields = {};
29027				},
29028				addImageFields: function(name, value)
29029				{
29030					this.upload.imageFields[name] = value;
29031				},
29032				removeImageFields: function(name)
29033				{
29034					delete this.upload.imageFields[name];
29035				},
29036				clearFileFields: function()
29037				{
29038					this.upload.fileFields = {};
29039				},
29040				addFileFields: function(name, value)
29041				{
29042					this.upload.fileFields[name] = value;
29043				},
29044				removeFileFields: function(name)
29045				{
29046					delete this.upload.fileFields[name];
29047				}
29048			};
29049		},
29050
29051		// =s3
29052		uploads3: function()
29053		{
29054			return {
29055				send: function(file, e)
29056				{
29057					this.uploads3.executeOnSignedUrl(file, $.proxy(function(signedURL)
29058					{
29059						this.uploads3.sendToS3(file, signedURL, e);
29060					}, this));
29061				},
29062				executeOnSignedUrl: function(file, callback)
29063				{
29064					var xhr = new XMLHttpRequest();
29065					var mark = (this.opts.s3.search(/\?/) === -1) ? '?' : '&';
29066
29067					xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
29068
29069					// hack to pass bytes through unprocessed.
29070					if (xhr.overrideMimeType)
29071					{
29072						xhr.overrideMimeType('text/plain;
29072 charset=x-user-defined');
29073					}
29074
29075					var that = this;
29076					xhr.onreadystatechange = function(e)
29077					{
29078						if (this.readyState === 4 && this.status === 200)
29079						{
29080							that.progress.show();
29081							callback(decodeURIComponent(this.responseText));
29082						}
29083					};
29084
29085					xhr.send();
29086				},
29087				createCORSRequest: function(method, url)
29088				{
29089					var xhr = new XMLHttpRequest();
29090					if ("withCredentials" in xhr)
29091					{
29092						xhr.open(method, url, true);
29093					}
29094					else if (typeof XDomainRequest !== "undefined")
29095					{
29096						xhr = new XDomainRequest();
29097						xhr.open(method, url);
29098					}
29099					else
29100					{
29101						xhr = null;
29102					}
29103
29104					return xhr;
29105				},
29106
29107                sendToS3: function(file, url, e)
29108                {
29109                    var xhr = this.uploads3.createCORSRequest('PUT', url);
29110                    if (!xhr)
29111                    {
29112                        return;
29113                    }
29114
29115                    xhr.onload = $.proxy(function()
29116                    {
29117                        var json;
29118                        this.progress.hide();
29119
29120                        if (xhr.status !== 200)
29121                        {
29122                            // error
29123                            json = { error: true };
29124                            this.upload.callback(json, this.upload.direct, xhr);
29125
29126                            return;
29127                        }
29128
29129                        var s3file = url.split('?');
29130                        if (!s3file[0])
29131                        {
29132                            // url parsing is fail
29133                            return false;
29134                        }
29135
29136
29137                        if (!this.upload.direct)
29138                        {
29139                            this.upload.$droparea.removeClass('drag-drop');
29140                        }
29141
29142                        json = { url: s3file[0], id: s3file[0], s3: true };
29143                        if (this.upload.type === 'file')
29144                        {
29145                            var arr = s3file[0].split('/');
29146                            json.name = arr[arr.length-1];
29147                        }
29148
29149                        this.upload.callback(json, this.upload.direct, e);
29150
29151
29152                    }, this);
29153
29154                    xhr.onerror = function() {};
29155                    xhr.upload.onprogress = function(e) {};
29156
29157                    xhr.setRequestHeader('Content-Type', file.type);
29158                    xhr.setRequestHeader('x-amz-acl', 'public-read');
29159
29160                    xhr.send(file);
29161
29162                }
29163			};
29164		},
29165
29166		// =utils
29167		utils: function()
29168		{
29169			return {
29170				isEmpty: function(html)
29171				{
29172					html = (typeof html === 'undefined') ? this.core.editor().html() : html;
29173
29174					html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
29175					html = html.replace(/&nbsp;/gi, '');
29176					html = html.replace(/<\/?br\s?\/?>/g, '');
29177					html = html.replace(/\s/g, '');
29178					html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
29179					html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
29180					html = html.replace(/<source(.*?[^>])>$/i, 'source');
29181
29182					// remove empty tags
29183					html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
29184					html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
29185
29186					html = $.trim(html);
29187
29188					return html === '';
29189				},
29190				isElement: function(obj)
29191				{
29192					try {
29193						// Using W3 DOM2 (works for FF, Opera and Chrome)
29194						return obj instanceof HTMLElement;
29195					}
29196					catch(e)
29197					{
29198						return (typeof obj === "object") && (obj.nodeType === 1) && (typeof obj.style === "object") && (typeof obj.ownerDocument === "object");
29199					}
29200				},
29201				strpos: function(haystack, needle, offset)
29202				{
29203					var i = haystack.indexOf(needle, offset);
29204					return i >= 0 ? i : false;
29205				},
29206				dataURItoBlob: function(dataURI)
29207				{
29208					var byteString;
29209					if (dataURI.split(',')[0].indexOf('base64') >= 0)
29210					{
29211						byteString = atob(dataURI.split(',')[1]);
29212					}
29213					else
29214					{
29215						byteString = unescape(dataURI.split(',')[1]);
29216					}
29217
29218					var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
29219
29220					var ia = new Uint8Array(byteString.length);
29221					for (var i = 0; i < byteString.length; i++)
29222					{
29223						ia[i] = byteString.charCodeAt(i);
29224					}
29225
29226					return new Blob([ia], { type:mimeString });
29227				},
29228				getOuterHtml: function(el)
29229				{
29230					return $('<div>').append($(el).eq(0).clone()).html();
29231				},
29232				cloneAttributes: function(from, to)
29233				{
29234					from = from[0] || from;
29235					to = $(to);
29236
29237					var attrs = from.attributes;
29238					var len = attrs.length;
29239					while (len--)
29240					{
29241					    var attr = attrs[len];
29242					    to.attr(attr.name, attr.value);
29243					}
29244
29245					return to;
29246				},
29247				breakBlockTag: function()
29248				{
29249					var block = this.selection.block();
29250					if (!block)
29251					{
29252    					return false;
29253					}
29254
29255					var isEmpty = this.utils.isEmpty(block.innerHTML);
29256
29257					var tag = block.tagName.toLowerCase();
29258					if (tag === 'pre' || tag === 'li' || tag === 'td' || tag === 'th')
29259					{
29260						return false;
29261					}
29262
29263					if (!isEmpty && this.utils.isStartOfElement(block))
29264					{
29265						return { $block: $(block), $next: $(block).next(), type: 'start' };
29266					}
29267					else if (!isEmpty && this.utils.isEndOfElement(block))
29268					{
29269						return { $block: $(block), $next: $(block).next(), type: 'end' };
29270					}
29271					else
29272					{
29273						var endOfNode = this.selection.extractEndOfNode(block);
29274						var $nextPart = $('<' + tag + ' />').append(endOfNode);
29275
29276						$nextPart = this.utils.cloneAttributes(block, $nextPart);
29277						$(block).after($nextPart);
29278
29279						return { $block: $(block), $next: $nextPart, type: 'break' };
29280					}
29281				},
29282				// tag detection
29283				inBlocks: function(tags)
29284				{
29285					tags = ($.isArray(tags)) ? tags : [tags];
29286
29287					var blocks = this.selection.blocks();
29288					var len = blocks.length;
29289					var contains = false;
29290					for (var i = 0; i < len; i++)
29291					{
29292						if (blocks[i] !== false)
29293						{
29294							var tag = blocks[i].tagName.toLowerCase();
29295
29296							if ($.inArray(tag, tags) !== -1)
29297							{
29298								contains = true;
29299							}
29300						}
29301					}
29302
29303					return contains;
29304
29305				},
29306				inInlines: function(tags)
29307				{
29308					tags = ($.isArray(tags)) ? tags : [tags];
29309
29310					var inlines = this.selection.inlines();
29311					var len = inlines.length;
29312					var contains = false;
29313					for (var i = 0; i < len; i++)
29314					{
29315						var tag = inlines[i].tagName.toLowerCase();
29316
29317						if ($.inArray(tag, tags) !== -1)
29318						{
29319							contains = true;
29320						}
29321					}
29322
29323					return contains;
29324
29325				},
29326				isTag: function(current, tag)
29327				{
29328					var element = $(current).closest(tag, this.core.editor()[0]);
29329					if (element.length === 1)
29330					{
29331						return element[0];
29332					}
29333
29334					return false;
29335				},
29336				isBlock: function(block)
29337				{
29338					if (block === null)
29339					{
29340						return false;
29341					}
29342
29343					block = block[0] || block;
29344
29345					return block && this.utils.isBlockTag(block.tagName);
29346				},
29347				isBlockTag: function(tag)
29348				{
29349					return (typeof tag === 'undefined') ? false : this.reIsBlock.test(tag);
29350				},
29351				isInline: function(inline)
29352				{
29353					inline = inline[0] || inline;
29354
29355					return inline && this.utils.isInlineTag(inline.tagName);
29356				},
29357				isInlineTag: function(tag)
29358				{
29359					return (typeof tag === 'undefined') ? false : this.reIsInline.test(tag);
29360				},
29361				// parents detection
29362				isRedactorParent: function(el)
29363				{
29364					if (!el)
29365					{
29366						return false;
29367					}
29368
29369					if ($(el).parents('.redactor-in').length === 0 || $(el).hasClass('redactor-in'))
29370					{
29371						return false;
29372					}
29373
29374					return el;
29375				},
29376				isCurrentOrParentHeader: function()
29377				{
29378					return this.utils.isCurrentOrParent(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
29379				},
29380				isCurrentOrParent: function(tagName)
29381				{
29382					var parent = this.selection.parent();
29383					var current = this.selection.current();
29384
29385					if ($.isArray(tagName))
29386					{
29387						var matched = 0;
29388						$.each(tagName, $.proxy(function(i, s)
29389						{
29390							if (this.utils.isCurrentOrParentOne(current, parent, s))
29391							{
29392								matched++;
29393							}
29394						}, this));
29395
29396						return (matched === 0) ? false : true;
29397					}
29398					else
29399					{
29400						return this.utils.isCurrentOrParentOne(current, parent, tagName);
29401					}
29402				},
29403				isCurrentOrParentOne: function(current, parent, tagName)
29404				{
29405					tagName = tagName.toUpperCase();
29406
29407					return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
29408				},
29409				isEditorRelative: function()
29410				{
29411					var position = this.core.editor().css('position');
29412					var arr = ['absolute', 'fixed', 'relative'];
29413
29414					return ($.inArray(arr, position) !== -1);
29415				},
29416				setEditorRelative: function()
29417				{
29418					this.core.editor().addClass('redactor-relative');
29419				},
29420				// scroll
29421				getScrollTarget: function()
29422				{
29423    				var $scrollTarget = $(this.opts.scrollTarget);
29424
29425    				return ($scrollTarget.length !== 0) ? $scrollTarget : $(document);
29426				},
29427				freezeScroll: function()
29428				{
29429					this.freezeScrollTop = this.utils.getScrollTarget().scrollTop();
29430					this.utils.getScrollTarget().scrollTop(this.freezeScrollTop);
29431				},
29432				unfreezeScroll: function()
29433				{
29434					if (typeof this.freezeScrollTop === 'undefined')
29435					{
29436						return;
29437					}
29438
29439					this.utils.getScrollTarget().scrollTop(this.freezeScrollTop);
29440				},
29441				saveScroll: function()
29442				{
29443					this.tmpScrollTop = this.utils.getScrollTarget().scrollTop();
29444				},
29445				restoreScroll: function()
29446				{
29447					if (typeof this.tmpScrollTop === 'undefined')
29448					{
29449						return;
29450					}
29451
29452					this.utils.getScrollTarget().scrollTop(this.tmpScrollTop);
29453				},
29454				isStartOfElement: function(element)
29455				{
29456					if (typeof element === 'undefined')
29457					{
29458						element = this.selection.block();
29459						if (!element)
29460						{
29461							return false;
29462						}
29463					}
29464
29465					return (this.offset.get(element) === 0) ? true : false;
29466				},
29467				isEndOfElement: function(element)
29468				{
29469					if (typeof element === 'undefined')
29470					{
29471						element = this.selection.block();
29472						if (!element)
29473						{
29474							return false;
29475						}
29476					}
29477
29478					var text = $.trim($(element).text()).replace(/[\t\n\r\n]/g, '').replace(/\u200B/g, '');
29479					var offset = this.offset.get(element);
29480
29481					return (offset === text.length) ? true : false;
29482				},
29483				removeEmptyAttr: function(el, attr)
29484				{
29485					var $el = $(el);
29486					if (typeof $el.attr(attr) === 'undefined')
29487					{
29488						return true;
29489					}
29490
29491					if ($el.attr(attr) === '')
29492					{
29493						$el.removeAttr(attr);
29494						return true;
29495					}
29496
29497					return false;
29498				},
29499				replaceToTag: function(node, tag)
29500				{
29501					var replacement;
29502					$(node).replaceWith(function()
29503					{
29504						replacement = $('<' + tag + ' />').append($(this).contents());
29505
29506						for (var i = 0; i < this.attributes.length; i++)
29507						{
29508							replacement.attr(this.attributes[i].name, this.attributes[i].value);
29509						}
29510
29511						return replacement;
29512					});
29513
29514					return replacement;
29515				},
29516				// select all
29517				isSelectAll: function()
29518				{
29519					return this.selectAll;
29520				},
29521				enableSelectAll: function()
29522				{
29523					this.selectAll = true;
29524				},
29525				disableSelectAll: function()
29526				{
29527					this.selectAll = false;
29528				},
29529				disableBodyScroll: function()
29530				{
29531					var $body = $('html');
29532					var windowWidth = window.innerWidth;
29533					if (!windowWidth)
29534					{
29535						var documentElementRect = document.documentElement.getBoundingClientRect();
29536						windowWidth = documentElementRect.right - Math.abs(documentElementRect.left);
29537					}
29538
29539					var isOverflowing = document.body.clientWidth < windowWidth;
29540					var scrollbarWidth = this.utils.measureScrollbar();
29541
29542					$body.css('overflow', 'hidden');
29543					if (isOverflowing)
29544					{
29545						$body.css('padding-right', scrollbarWidth);
29546					}
29547				},
29548				measureScrollbar: function()
29549				{
29550					var $body = $('body');
29551					var scrollDiv = document.createElement('div');
29552					scrollDiv.className = 'redactor-scrollbar-measure';
29553
29554					$body.append(scrollDiv);
29555					var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
29556					$body[0].removeChild(scrollDiv);
29557					return scrollbarWidth;
29558				},
29559				enableBodyScroll: function()
29560				{
29561					$('html').css({ 'overflow': '', 'padding-right': '' });
29562					$('body').remove('redactor-scrollbar-measure');
29563				},
29564				appendFields: function(appendFields, data)
29565				{
29566					if (!appendFields)
29567					{
29568						return data;
29569					}
29570					else if (typeof appendFields === 'object')
29571					{
29572    					$.each(appendFields, function(k, v)
29573    					{
29574    						if (v !== null && v.toString().indexOf('#') === 0)
29575    						{
29576        						v = $(v).val();
29577                            }
29578
29579    						data.append(k, v);
29580
29581    					});
29582
29583    					return data;
29584					}
29585
29586					var $fields = $(appendFields);
29587					if ($fields.length === 0)
29588					{
29589						return data;
29590					}
29591					else
29592					{
29593						var str = '';
29594						$fields.each(function()
29595						{
29596							data.append($(this).attr('name'), $(this).val());
29597						});
29598
29599						return data;
29600					}
29601				},
29602				appendForms: function(appendForms, data)
29603				{
29604					if (!appendForms)
29605					{
29606						return data;
29607					}
29608
29609					var $forms = $(appendForms);
29610					if ($forms.length === 0)
29611					{
29612						return data;
29613					}
29614					else
29615					{
29616						var formData = $forms.serializeArray();
29617
29618						$.each(formData, function(z,f)
29619						{
29620							data.append(f.name, f.value);
29621						});
29622
29623						return data;
29624					}
29625				},
29626				isRgb: function(str)
29627				{
29628                    return (str.search(/^rgb/i) === 0);
29629				},
29630				rgb2hex: function(rgb)
29631				{
29632                    rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
29633
29634                    return (rgb && rgb.length === 4) ? "#" +
29635                    ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
29636                    ("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
29637                    ("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : '';
29638                },
29639
29640				// #backward
29641				isCollapsed: function()
29642				{
29643					return this.selection.isCollapsed();
29644				},
29645				isMobile: function()
29646				{
29647					return this.detect.isMobile();
29648				},
29649				isDesktop: function()
29650				{
29651					return this.detect.isDesktop();
29652				},
29653				isPad: function()
29654				{
29655					return this.detect.isIpad();
29656				}
29657
29658			};
29659		},
29660
29661		// #backward
29662		browser: function()
29663		{
29664			return {
29665				webkit: function()
29666				{
29667					return this.detect.isWebkit();
29668				},
29669				ff: function()
29670				{
29671					return this.detect.isFirefox();
29672				},
29673				ie: function()
29674				{
29675					return this.detect.isIe();
29676				}
29677			};
29678		}
29679	};
29680
29681	$(window).on('load.tools.redactor', function()
29682	{
29683		$('[data-tools="redactor"]').redactor();
29684	});
29685
29686	// constructor
29687	Redactor.prototype.init.prototype = Redactor.prototype;
29688
29689})(jQuery);
29690
29691(function($)
29692{
29693	$.fn.redactorAnimation = function(animation, options, callback)
29694	{
29695		return this.each(function()
29696		{
29697			new redactorAnimation(this, animation, options, callback);
29698		});
29699	};
29700
29701	function redactorAnimation(element, animation, options, callback)
29702	{
29703		// default
29704		var opts = {
29705			duration: 0.5,
29706			iterate: 1,
29707			delay: 0,
29708			prefix: 'redactor-',
29709			timing: 'linear'
29710		};
29711
29712		this.animation = animation;
29713		this.slide = (this.animation === 'slideDown' || this.animation === 'slideUp');
29714		this.$element = $(element);
29715		this.prefixes = ['', '-moz-', '-o-animation-', '-webkit-'];
29716		this.queue = [];
29717
29718		// options or callback
29719		if (typeof options === 'function')
29720		{
29721			callback = options;
29722			this.opts = opts;
29723		}
29724		else
29725		{
29726			this.opts = $.extend(opts, options);
29727		}
29728
29729		// slide
29730		if (this.slide)
29731		{
29732			this.$element.height(this.$element.height());
29733		}
29734
29735		// init
29736		this.init(callback);
29737
29738	}
29739
29740	redactorAnimation.prototype = {
29741
29742		init: function(callback)
29743		{
29744			this.queue.push(this.animation);
29745
29746			this.clean();
29747
29748			if (this.animation === 'show')
29749			{
29750				this.opts.timing = 'linear';
29751				this.$element.removeClass('hide').show();
29752
29753				if (typeof callback === 'function')
29754				{
29755					callback(this);
29756				}
29757			}
29758			else if (this.animation === 'hide')
29759			{
29760				this.opts.timing = 'linear';
29761				this.$element.hide();
29762
29763				if (typeof callback === 'function')
29764				{
29765					callback(this);
29766				}
29767			}
29768			else
29769			{
29770				this.animate(callback);
29771			}
29772
29773		},
29774		animate: function(callback)
29775		{
29776			this.$element.addClass('redactor-animated').css('display', '').removeClass('hide');
29777			this.$element.addClass(this.opts.prefix + this.queue[0]);
29778
29779			this.set(this.opts.duration + 's', this.opts.delay + 's', this.opts.iterate, this.opts.timing);
29780
29781			var _callback = (this.queue.length > 1) ? null : callback;
29782			this.complete('AnimationEnd', $.proxy(function()
29783			{
29784				if (this.$element.hasClass(this.opts.prefix + this.queue[0]))
29785				{
29786					this.clean();
29787					this.queue.shift();
29788
29789					if (this.queue.length)
29790					{
29791						this.animate(callback);
29792					}
29793				}
29794
29795			}, this), _callback);
29796		},
29797		set: function(duration, delay, iterate, timing)
29798		{
29799			var len = this.prefixes.length;
29800
29801			while (len--)
29802			{
29803				this.$element.css(this.prefixes[len] + 'animation-duration', duration);
29804				this.$element.css(this.prefixes[len] + 'animation-delay', delay);
29805				this.$element.css(this.prefixes[len] + 'animation-iteration-count', iterate);
29806				this.$element.css(this.prefixes[len] + 'animation-timing-function', timing);
29807			}
29808
29809		},
29810		clean: function()
29811		{
29812			this.$element.removeClass('redactor-animated');
29813			this.$element.removeClass(this.opts.prefix + this.queue[0]);
29814
29815			this.set('', '', '', '');
29816
29817		},
29818		complete: function(type, make, callback)
29819		{
29820			this.$element.one(type.toLowerCase() + ' webkit' + type + ' o' + type + ' MS' + type, $.proxy(function()
29821			{
29822				if (typeof make === 'function')
29823				{
29824					make();
29825				}
29826
29827				if (typeof callback === 'function')
29828				{
29829					callback(this);
29830				}
29831
29832				// hide
29833				var effects = ['fadeOut', 'slideUp', 'zoomOut', 'slideOutUp', 'slideOutRight', 'slideOutLeft'];
29834				if ($.inArray(this.animation, effects) !== -1)
29835				{
29836					this.$element.css('display', 'none');
29837				}
29838
29839				// slide
29840				if (this.slide)
29841				{
29842					this.$element.css('height', '');
29843				}
29844
29845			}, this));
29846
29847		}
29848	};
29849
29850})(jQuery);
29851$(function () {
29852    // Pass authenticity_token
29853    var params = '[name="authenticity_token"]';
29854    // Set global settings
29855    $.Redactor.settings = {
29856      /*
29857      imageUpload: '/redactor2_rails/images',
29858      imageUploadFields: params,
29859      fileUpload: '/redactor2_rails/files',
29860      fileUploadFields: params,
29861      callbacks: {
29862        imageUploadError: function(json)
29863        {
29864            console.log(json.error);
29865            console.log(json.anothermessage);
29866        },
29867        fileUploadError: function(json)
29868        {
29869            console.log(json.error);
29870            console.log(json.anothermessage);
29871        }
29872      }
29873      */
29874    };
29875    // Initialize Redactor
29876    //$('.redactor').redactor();
29877});
29878(function($)
29879{
29880	$.Redactor.prototype.alignment = function()
29881	{
29882		return {
29883			langs: {
29884				en: {
29885    			"align": "Align",
29886					"align-left": "Align Left",
29887					"align-center": "Align Centre",
29888					"align-right": "Align Right",
29889				}
29890			},
29891			init: function()
29892			{
29893				var that = this;
29894				var dropdown = {};
29895
29896				dropdown.left = { title: that.lang.get('align-left'), func: that.alignment.setLeft };
29897				dropdown.center = { title: that.lang.get('align-center'), func: that.alignment.setCenter };
29898				dropdown.right = { title: that.lang.get('align-right'), func: that.alignment.setRight };
29899				//dropdown.justify = { title: that.lang.get('align-justify'), func: that.alignment.setJustify };
29900
29901				var button = this.button.addAfter('lists', 'alignment', this.lang.get('align'));
29902                this.button.setIcon(button, '<i class="re-icon-alignment"></i>');
29903				this.button.addDropdown(button, dropdown);
29904			},
29905			removeAlign: function()
29906			{
29907    		    this.block.removeClass('text-center');
29908    		    this.block.removeClass('text-right');
29909    		    this.block.removeClass('text-justify');
29910			},
29911			setLeft: function()
29912			{
29913				this.buffer.set();
29914				this.alignment.removeAlign();
29915			},
29916			setCenter: function()
29917			{
29918				this.buffer.set();
29919				this.alignment.removeAlign();
29920				this.block.addClass('text-center');
29921				this.core.editor().focus()
29922			},
29923			setRight: function()
29924			{
29925				this.buffer.set();
29926				this.alignment.removeAlign();
29927				this.block.addClass('text-right');
29928				this.core.editor().focus()
29929			},
29930			setJustify: function()
29931			{
29932				this.buffer.set();
29933				this.alignment.removeAlign();
29934				this.block.addClass('text-justify');
29935				this.core.editor().focus()
29936			}
29937		};
29938	};
29939})(jQuery);
29940(function($)
29941{
29942	$.Redactor.prototype.filemanager = function()
29943	{
29944		return {
29945			langs: {
29946				en: {
29947					"upload": "Upload",
29948					"choose": "Choose"
29949				}
29950			},
29951			init: function()
29952			{
29953				if (!this.opts.fileManagerJson)
29954				{
29955					return;
29956				}
29957
29958				this.modal.addCallback('file', this.filemanager.load);
29959			},
29960			load: function()
29961			{
29962				var $box = $('<div  style="overflow: auto; height: 300px; display: none;" class="redactor-modal-tab" data-title="Choose">').hide();
29963				this.modal.getModal().append($box);
29964
29965
29966				$.ajax({
29967				  dataType: "json",
29968				  cache: false,
29969				  url: this.opts.fileManagerJson,
29970				  success: $.proxy(function(data)
29971					{
29972						var ul = $('<ul id="redactor-modal-list">');
29973						$.each(data, $.proxy(function(key, val)
29974						{
29975
29976							var a = $('<a href="#" data-params="' + encodeURI(JSON.stringify(val)) + '" class="redactor-file-manager-link">' + val.title + ' <span style="font-size: 11px; color: #888;">' + val.name + '</span> <span style="position: absolute; right: 10px; font-size: 11px; color: #888;">(' + val.size + ')</span></a>');
29977							var li = $('<li />');
29978
29979							a.on('click', $.proxy(this.filemanager.insert, this));
29980
29981							li.append(a);
29982							ul.append(li);
29983
29984						}, this));
29985
29986						$box.append(ul);
29987
29988
29989					}, this)
29990				});
29991
29992			},
29993			insert: function(e)
29994			{
29995				e.preventDefault();
29996
29997				var $el = $(e.target).closest('.redactor-file-manager-link');
29998				var json = $.parseJSON(decodeURI($el.attr('data-params')));
29999
30000				this.file.insert(json, null);
30001			}
30002		};
30003	};
30004})(jQuery);
30005(function($)
30006{
30007  $.Redactor.prototype.hubblebutton = function()
30008  {
30009      return {
30010          init: function()
30011          {
30012            var button = this.button.add('button', 'Button');
30013            this.button.setIcon(button, '<i class="mdi mdi-code-greater-than"></i>');
30014            this.button.addCallback(button, this.hubblebutton.toggle);
30015          },
30016          toggle: function()
30017          {
30018            this.inline.toggleClass('button');
30019          }
30020      };
30021  };
30022})(jQuery);
30023(function($)
30024{
30025  $.Redactor.prototype.hubbledo = function()
30026  {
30027    return {
30028      init: function()
30029      {
30030        var undobutton = this.button.add('undo', 'Undo');
30031        var redobutton = this.button.add('redo', 'Redo');
30032
30033        this.button.setIcon(undobutton, '<i class="mdi mdi-undo"></i>');
30034        this.button.setIcon(redobutton, '<i class="mdi mdi-redo"></i>');
30035
30036        this.button.addCallback(undobutton, this.buffer.undo);
30037        this.button.addCallback(redobutton, this.buffer.redo);
30038      }
30039    };
30040  };
30041})(jQuery);
30042(function($)
30043{
30044  $.Redactor.prototype.hubbleimage = function()
30045  {
30046      return {
30047          init: function()
30048          {
30049            this.modal.addCallback('image-edit', this.hubbleimage.load);
30050          },
30051          load: function()
30052          {
30053            $('input#redactor-image-link').parents('section').append('<div class="info tips">Links to other websites must start with <em>http://</em> or <em>https://</em><br />Links to pages in this website should start with <em>/</em></div>');
30054            $('input#redactor-image-link').keyup(function(){
30055              $('button#redactor-modal-button-action').prop('disabled', false);
30056              $('div.info.errors', $(this).parents('section')).remove();
30057              if ($('input#redactor-image-link').val().indexOf('lvh.me:3000') >= 0) {
30058                $('button#redactor-modal-button-action').prop('disabled', true);
30059                $(this).parents('section').append('<div class="info errors">If you wish to link to an internal page, add the URL from the first <em>/</em> after the domain name (e.g. <em>/pages/1</em>)</div>');
30060              }
30061            });
30062          }
30063      };
30064  };
30065})(jQuery);
30066(function($)
30067{
30068  $.Redactor.prototype.hubblelink = function()
30069  {
30070    return {
30071      init: function()
30072      {
30073        this.modal.addCallback('link', this.hubblelink.load);
30074      },
30075      load: function()
30076      {
30077        var $link_anchor = '';
30078        if (window.location.href.indexOf('_broadcasts/') > -1) {
30079          $link_anchor = 'https://www.lvh.me:3000';
30080        }
30081        var $section_type = $('<section class="type"></section>');
30082        $section_type.append($('<label for="redactor-choose-type">Destination</label>'));
30083        var $select_type = $('<select id="redactor-choose-type"></select>');
30084        $select_type.append($('<option value="" selected="selected">Please select...</option>'));
30085        $select_type.append($('<option value="page">Page</option>'));
30086        $select_type.append($('<option id="redactor-option-article" value="article">Article</option>'));
30087        $select_type.append($('<option id="redactor-option-event" value="event">Event</option>'));
30088        $select_type.append($('<option id="redactor-option-resource" value="resource">Resource</option>'));
30089        $select_type.append($('<option id="redactor-option-story" value="story">Story</option>'));
30090        $select_type.append($('<option value="custom">Custom URL</option>'));
30091        $select_type.append($('<option value="email">Email address</option>'));
30092        $section_type.append($select_type);
30093        var $section_page = $('<section class="choose page" data-type="page"></section>');
30094        $section_page.append($('<label for="redactor-choose-page">Page</label>'));
30095        $section_page.append($('<select id="redactor-choose-page"><option value="" selected="selected">Please select...</option></select>'));
30096        var $section_article = $('<section class="choose article" data-type="article"></section>');
30097        $section_article.append($('<label for="redactor-choose-article">Article</label>'));
30098        $section_article.append($('<select id="redactor-choose-article"><option value="" selected="selected">Please select...</option></select>'));
30099        var $section_event = $('<section class="choose event" data-type="event"></section>');
30100        $section_event.append($('<label for="redactor-choose-event">Event</label>'));
30101        $section_event.append($('<select id="redactor-choose-event"><option value="" selected="selected">Please select...</option></select>'));
30102        var $section_resource = $('<section class="choose resource" data-type="resource"></section>');
30103        $section_resource.append($('<label for="redactor-choose-resource">Resource</label>'));
30104        $section_resource.append($('<select id="redactor-choose-resource"><option value="" selected="selected">Please select...</option></select>'));
30105        var $section_story = $('<section class="choose story" data-type="story"></section>');
30106        $section_story.append($('<label for="redactor-choose-story">Story</label>'));
30107        $section_story.append($('<select id="redactor-choose-story"><option value="" selected="selected">Please select...</option></select>'));
30108        $('div.redactor-modal-tab').prepend($section_story);
30109        $('div.redactor-modal-tab').prepend($section_resource);
30110        $('div.redactor-modal-tab').prepend($section_event);
30111        $('div.redactor-modal-tab').prepend($section_article);
30112        $('div.redactor-modal-tab').prepend($section_page);
30113        $('div.redactor-modal-tab').prepend($section_type);
30114        $.ajax({
30115          type: 'GET',
30116          url: '/admin/pages',
30117          data: {
30118            sort: {
30119              title: 'asc'
30120            }
30121          },
30122          dataType: 'json',
30123          success: function($result) {
30124            $.each($result, function($index, $value){
30125              $('select#redactor-choose-page').append('<option value="'+$link_anchor+'/pages/'+$result[$index].path+'">'+$result[$index].title+'</option>');
30126            });
30127          }
30128        });
30129        $.ajax({
30130          type: 'GET',
30131          url: '/admin/events',
30132          data: {
30133            sort: {
30134              title: 'asc'
30135            }
30136          },
30137          dataType: 'json',
30138          success: function($result) {
30139            if ($result.length == 0) {
30140              $('option#redactor-option-event').hide();
30141            }
30142            $.each($result, function($index, $value){
30143              $('select#redactor-choose-event').append('<option value="'+$link_anchor+'/events/'+$result[$index].path+'">'+$result[$index].title+'</option>');
30144            });
30145          },
30146          error: function($request, $status, $error) {
30147            $('option#redactor-option-event').hide();
30148          }
30149        });
30150        $.ajax({
30151          type: 'GET',
30152          url: '/admin/articles',
30153          data: {
30154            sort: {
30155              title: 'asc'
30156            }
30157          },
30158          dataType: 'json',
30159          success: function($result) {
30160            if ($result.length == 0) {
30161              $('option#redactor-option-article').hide();
30162            }
30163            $.each($result, function($index, $value){
30164              $('select#redactor-choose-article').append('<option value="'+$link_anchor+'/articles/'+$result[$index].path+'">'+$result[$index].title+'</option>');
30165            });
30166          },
30167          error: function($request, $status, $error) {
30168            $('option#redactor-option-article').hide();
30169          }
30170        });
30171        $.ajax({
30172          type: 'GET',
30173          url: '/admin/resources',
30174          data: {
30175            sort: {
30176              title: 'asc'
30177            }
30178          },
30179          dataType: 'json',
30180          success: function($result) {
30181            if ($result.length == 0) {
30182              $('option#redactor-option-resource').hide();
30183            }
30184            $.each($result, function($index, $value){
30185              $('select#redactor-choose-resource').append('<option value="'+$link_anchor+'/resources/'+$result[$index].path+'">'+$result[$index].title+'</option>');
30186            });
30187          },
30188          error: function($request, $status, $error) {
30189            $('option#redactor-option-resource').hide();
30190          }
30191        });
30192        $.ajax({
30193          type: 'GET',
30194          url: '/admin/stories',
30195          data: {
30196            sort: {
30197              title: 'asc'
30198            }
30199          },
30200          dataType: 'json',
30201          success: function($result) {
30202            if ($result.length == 0) {
30203              $('option#redactor-option-story').hide();
30204            }
30205            $.each($result, function($index, $value){
30206              $('select#redactor-choose-story').append('<option value="'+$link_anchor+'/stories/'+$result[$index].path+'">'+$result[$index].title+'</option>');
30207            });
30208          },
30209          error: function($request, $status, $error) {
30210            $('option#redactor-option-story').hide();
30211          }
30212        });
30213        $('section.choose').hide();
30214        $('input#redactor-link-url').parents('section').hide();
30215        var $href = this.link.buildLinkFromElement(this.link.is()).url;
30216        if ($href != '') {
30217          $found = false;
30218          $('section.choose select option').each(function(){
30219            if ($href == $(this).val()) {
30220              $('select#redactor-choose-type').val($(this).parents('section').data('type'));
30221              $(this).parents('section').show();
30222              $(this).prop('selected', true);
30223              $found = true;
30224            }
30225          });
30226          if (!$found) {
30227            $('select#redactor-choose-type').val('custom');
30228            $('input#redactor-link-url').parents('section').show();
30229          }
30230        }
30231        $('select#redactor-choose-type').change(function(){
30232          $('section.choose').hide();
30233          $('section.choose select').val('');
30234          $('input#redactor-link-url').val('');
30235          $('input#redactor-link-url').parents('section').hide();
30236          if ($(this).val() != '') {
30237            if ($(this).val() == 'custom') {
30238              $('input#redactor-link-url').parents('section').show();
30239              $('input#redactor-link-url').val('https://');
30240            } else if ($(this).val() == 'email') {
30241              $('input#redactor-link-url').parents('section').show();
30242              $('input#redactor-link-url').val('mailto:');
30243            } else {
30244              $('section.'+$(this).val()).show();
30245            }
30246          }
30247        });
30248        $('section.choose select').change(function(){
30249          $('button#redactor-modal-button-action').prop('disabled', false);
30250          $('button#redactor-modal-button-action').css('opacity', '1');
30251          $('div.info.errors', $(this).parents('section')).remove();
30252          $('input#redactor-link-url').val($(this).val());
30253        });
30254        $('input#redactor-link-url').keyup(function(){
30255          $('button#redactor-modal-button-action').prop('disabled', false);
30256          $('button#redactor-modal-button-action').css('opacity', '1');
30257          $('div.info.errors', $(this).parents('section')).remove();
30258          if (
30259            ($('input#redactor-link-url').val().indexOf('http://') != 0) &&
30260            ($('input#redactor-link-url').val().indexOf('https://') != 0) &&
30261            ($('input#redactor-link-url').val().indexOf('mailto:') != 0) &&
30262            ($('input#redactor-link-url').val().indexOf('tel:') != 0) &&
30263            ($('input#redactor-link-url').val().indexOf('/') != 0) &&
30264            ($('input#redactor-link-url').val().indexOf('#') != 0)
30265          ) {
30266            $('button#redactor-modal-button-action').prop('disabled', true);
30267            $('button#redactor-modal-button-action').css('opacity', '0.5');
30268            $(this).parents('section').append('<div class="info errors">Links to other websites must start with <em>http://</em> or <em>https://</em><br />Links to an email address must start with <em>mailto:</em><br />Links to a telephone number must start with <em>tel:</em><br />Links to pages within this website must start with <em>/</em><br />Links to anchors within this page must start with <em>#</em></div>');
30269          }
30270          if ($('input#redactor-link-url').val().indexOf('lvh.me:3000') >= 0) {
30271            $('button#redactor-modal-button-action').prop('disabled', true);
30272            $('button#redactor-modal-button-action').css('opacity', '0.5');
30273            $(this).parents('section').append('<div class="info errors">If you wish to link to a page in this website, it is better to choose it from the lists above.</div>');
30274          }
30275        });
30276      }
30277    };
30278  };
30279})(jQuery);
30280(function($)
30281{
30282  $.Redactor.prototype.hubblesubsup = function()
30283  {
30284    return {
30285      init: function()
30286      {
30287        var subbutton = this.button.add('subscript', 'Subscript');
30288        var supbutton = this.button.add('superscript', 'Superscript');
30289
30290        this.button.setIcon(subbutton, '<i class="mdi mdi-format-subscript"></i>');
30291        this.button.setIcon(supbutton, '<i class="mdi mdi-format-superscript"></i>');
30292
30293        this.button.addCallback(subbutton, this.hubblesubsup.formatSub);
30294        this.button.addCallback(supbutton, this.hubblesubsup.formatSup);
30295      },
30296      formatSub: function()
30297      {
30298        this.inline.format('sub');
30299      },
30300      formatSup: function()
30301      {
30302        this.inline.format('sup');
30303      }
30304    };
30305  };
30306})(jQuery);
30307(function($)
30308{
30309  $.Redactor.prototype.hubbletoken = function()
30310  {
30311      return {
30312          init: function()
30313          {
30314            var that = this;
30315    				var dropdown = {};
30316
30317            dropdown.honorific = { title: 'Honorific', func: that.hubbletoken.insertHonorific };
30318            dropdown.first_name = { title: 'First name', func: that.hubbletoken.insertFirstName };
30319            dropdown.last_name = { title: 'Last name', func: that.hubbletoken.insertLastName };
30320            dropdown.organisation = { title: 'Organisation', func: that.hubbletoken.insertOrganisation };
30321            dropdown.create_account_button = { title: 'Create account button', func: that.hubbletoken.insertCreateAccountButton };
30322            dropdown.direct_debit_button = { title: 'Direct Debit button', func: that.hubbletoken.insertDirectDebitButton };
30323            dropdown.organisation_direct_debit_button = { title: 'Organisation Direct Debit button', func: that.hubbletoken.insertOrganisationDirectDebitButton };
30324
30325            var button = this.button.add('token', 'Token');
30326            this.button.setIcon(button, '<i class="mdi mdi-contain"></i>');
30327            this.button.addDropdown(button, dropdown);
30328          },
30329          insertHonorific: function()
30330          {
30331            html = '[honorific]'
30332            this.insert.html(html);
30333          },
30334          insertFirstName: function()
30335          {
30336            html = '[first_name]'
30337            this.insert.html(html);
30338          },
30339          insertLastName: function()
30340          {
30341            html = '[last_name]'
30342            this.insert.html(html);
30343          },
30344          insertEmail: function()
30345          {
30346            html = '[email]'
30347            this.insert.html(html);
30348          },
30349          insertAddress: function()
30350          {
30351            html = '[address]'
30352            this.insert.html(html);
30353          },
30354          insertPhone: function()
30355          {
30356            html = '[phone]'
30357            this.insert.html(html);
30358          },
30359          insertWebsite: function()
30360          {
30361            html = '[website]'
30362            this.insert.html(html);
30363          },
30364          insertOrganisation: function()
30365          {
30366            html = '[organisation]'
30367            this.insert.html(html);
30368          },
30369          insertCreateAccountButton: function()
30370          {
30371            html = '[create_account_button]'
30372            this.insert.html(html);
30373          },
30374          insertDirectDebitButton: function()
30375          {
30376            html = '[direct_debit_button]'
30377            this.insert.html(html);
30378          },
30379          insertOrganisationDirectDebitButton: function()
30380          {
30381            html = '[organisation_direct_debit_button]'
30382            this.insert.html(html);
30383          }
30384      };
30385  };
30386})(jQuery);
30387(function($)
30388{
30389	$.Redactor.prototype.imagemanager = function()
30390	{
30391		return {
30392			langs: {
30393				en: {
30394					"upload": "Upload",
30395					"choose": "Choose"
30396				}
30397			},
30398			init: function()
30399			{
30400				if (!this.opts.imageManagerJson)
30401				{
30402					return;
30403				}
30404
30405				this.modal.addCallback('image', this.imagemanager.load);
30406			},
30407			load: function()
30408			{
30409				var $box = $('<div style="overflow: auto; height: 300px; display: none;" class="redactor-modal-tab" data-title="Choose">');
30410				this.modal.getModal().append($box);
30411
30412				$.ajax({
30413					dataType: "json",
30414					cache: false,
30415					url: this.opts.imageManagerJson,
30416					success: $.proxy(function(data)
30417					{
30418						$.each(data, $.proxy(function(key, val)
30419						{
30420							// title
30421							var thumbtitle = '';
30422							if (typeof val.title !== 'undefined')
30423							{
30424								thumbtitle = val.title;
30425							}
30426
30427							var img = $('<img src="' + val.thumb + '"  data-params="' + encodeURI(JSON.stringify(val)) + '" style="width: 100px; height: 75px; cursor: pointer;" />');
30428							$box.append(img);
30429							$(img).click($.proxy(this.imagemanager.insert, this));
30430
30431						}, this));
30432
30433					}, this)
30434				});
30435
30436
30437			},
30438			insert: function(e)
30439			{
30440				var $el = $(e.target);
30441				var json = $.parseJSON(decodeURI($el.attr('data-params')));
30442
30443				this.image.insert(json, null);
30444			}
30445		};
30446	};
30447})(jQuery);
30448(function($)
30449{
30450	$.Redactor.prototype.source = function()
30451	{
30452		return {
30453			init: function()
30454			{
30455				var button = this.button.addFirst('html', 'HTML');
30456				this.button.setIcon(button, '<i class="re-icon-html"></i>');
30457				this.button.addCallback(button, this.source.toggle);
30458
30459				var style = {
30460					'width': '100%',
30461					'margin': '0',
30462					'background': '#1d1d1d',
30463					'box-sizing': 'border-box',
30464					'color': '#ccc',
30465					'font-size': '15px',
30466					'outline': 'none',
30467					'padding': '20px',
30468					'line-height': '24px',
30469					'font-family': 'Consolas, Menlo, Monaco, "Courier New", monospace'
30470				};
30471
30472				this.source.$textarea = $('<textarea />');
30473				this.source.$textarea.css(style).hide();
30474
30475				if (this.opts.type === 'textarea')
30476				{
30477					this.core.box().append(this.source.$textarea);
30478				}
30479				else
30480				{
30481					this.core.box().after(this.source.$textarea);
30482				}
30483
30484				this.core.element().on('destroy.callback.redactor', $.proxy(function()
30485				{
30486					this.source.$textarea.remove();
30487
30488				}, this));
30489
30490			},
30491			toggle: function()
30492			{
30493				return (this.source.$textarea.hasClass('open')) ? this.source.hide() : this.source.show();
30494			},
30495			setCaretOnShow: function()
30496			{
30497				this.source.offset = this.offset.get();
30498				var scroll = $(window).scrollTop();
30499
30500				var	width = this.core.editor().innerWidth();
30501				var height = this.core.editor().innerHeight();
30502
30503				// caret position sync
30504				this.source.start = 0;
30505				this.source.end = 0;
30506				var $editorDiv = $("<div/>").append($.parseHTML(this.core.editor().html(), document, true));
30507				var $selectionMarkers = $editorDiv.find("span.redactor-selection-marker");
30508
30509				if ($selectionMarkers.length > 0)
30510				{
30511					var editorHtml = $editorDiv.html().replace(/&amp;/g, '&');
30512
30513					if ($selectionMarkers.length === 1)
30514					{
30515						this.source.start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
30516						this.source.end = this.source.start;
30517					}
30518					else if ($selectionMarkers.length === 2)
30519					{
30520						this.source.start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
30521						this.source.end = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-2").prop("outerHTML")) - $editorDiv.find("#selection-marker-1").prop("outerHTML").toString().length;
30522					}
30523				}
30524			},
30525			setCaretOnHide: function(html)
30526			{
30527			    this.source.start = this.source.$textarea.get(0).selectionStart;
30528				this.source.end = this.source.$textarea.get(0).selectionEnd;
30529
30530				// if selection starts from end
30531				if (this.source.start > this.source.end && this.source.end > 0)
30532				{
30533					var tempStart = this.source.end;
30534					var tempEnd = this.source.start;
30535
30536					this.source.start = tempStart;
30537					this.source.end = tempEnd;
30538				}
30539
30540				this.source.start = this.source.enlargeOffset(html, this.source.start);
30541				this.source.end = this.source.enlargeOffset(html, this.source.end);
30542
30543				html = html.substr(0, this.source.start) + this.marker.html(1) + html.substr(this.source.start);
30544
30545				if (this.source.end > this.source.start)
30546				{
30547					var markerLength = this.marker.html(1).toString().length;
30548
30549					html = html.substr(0, this.source.end + markerLength) + this.marker.html(2) + html.substr(this.source.end + markerLength);
30550				}
30551
30552
30553				return html;
30554
30555			},
30556			hide: function()
30557			{
30558				this.source.$textarea.removeClass('open').hide();
30559				this.source.$textarea.off('.redactor-source');
30560
30561				var code = this.source.$textarea.val();
30562
30563				code = this.paragraphize.load(code);
30564				code = this.source.setCaretOnHide(code);
30565
30566				this.code.start(code);
30567				this.button.enableAll();
30568				this.core.editor().show().focus();
30569				this.selection.restore();
30570
30571                this.core.callback('visual');
30572			},
30573			show: function()
30574			{
30575				this.selection.save();
30576				this.source.setCaretOnShow();
30577
30578				var height = this.core.editor().height();
30579				var code = this.code.get();
30580
30581                // callback
30582                code = this.core.callback('source', code);
30583
30584				this.core.editor().hide();
30585				this.button.disableAll('html');
30586
30587				this.source.$textarea.val(code).height(height).addClass('open').show();
30588				this.source.$textarea.on('keyup.redactor-source', $.proxy(function()
30589				{
30590					if (this.opts.type === 'textarea')
30591					{
30592						this.core.textarea().val(this.source.$textarea.val());
30593					}
30594
30595				}, this));
30596
30597				this.marker.remove();
30598
30599				$(window).scrollTop(scroll);
30600
30601				if (this.source.$textarea[0].setSelectionRange)
30602				{
30603					this.source.$textarea[0].setSelectionRange(this.source.start, this.source.end);
30604				}
30605
30606				this.source.$textarea[0].scrollTop = 0;
30607
30608				setTimeout($.proxy(function()
30609				{
30610					this.source.$textarea.focus();
30611
30612				}, this), 0);
30613			},
30614			enlargeOffset: function(html, offset)
30615			{
30616				var htmlLength = html.length;
30617				var c = 0;
30618
30619				if (html[offset] === '>')
30620				{
30621					c++;
30622				}
30623				else
30624				{
30625					for(var i = offset; i <= htmlLength; i++)
30626					{
30627						c++;
30628
30629						if (html[i] === '>')
30630						{
30631							break;
30632						}
30633						else if (html[i] === '<' || i === htmlLength)
30634						{
30635							c = 0;
30636							break;
30637						}
30638					}
30639				}
30640
30641				return offset + c;
30642			}
30643		};
30644	};
30645})(jQuery);
30646(function($)
30647{
30648	$.Redactor.prototype.table = function()
30649	{
30650		return {
30651			langs: {
30652				en: {
30653					"table": "Table",
30654					"insert-table": "Insert table",
30655					"insert-row-above": "Insert row above",
30656					"insert-row-below": "Insert row below",
30657					"insert-column-left": "Insert column left",
30658					"insert-column-right": "Insert column right",
30659					"add-head": "Add head",
30660					"delete-head": "Delete head",
30661					"delete-column": "Delete column",
30662					"delete-row": "Delete row",
30663					"delete-table": "Delete table"
30664				}
30665			},
30666			init: function()
30667			{
30668				var dropdown = {};
30669
30670				dropdown.insert_table = {
30671					title: this.lang.get('insert-table'),
30672					func: this.table.insert,
30673					observe: {
30674						element: 'table',
30675						in: {
30676							attr: {
30677								'class': 'redactor-dropdown-link-inactive',
30678								'aria-disabled': true,
30679							}
30680						}
30681					}
30682				};
30683
30684				dropdown.insert_row_above = {
30685					title: this.lang.get('insert-row-above'),
30686					func: this.table.addRowAbove,
30687					observe: {
30688						element: 'table',
30689						out: {
30690							attr: {
30691								'class': 'redactor-dropdown-link-inactive',
30692								'aria-disabled': true,
30693							}
30694						}
30695					}
30696				};
30697
30698				dropdown.insert_row_below = {
30699					title: this.lang.get('insert-row-below'),
30700					func: this.table.addRowBelow,
30701					observe: {
30702						element: 'table',
30703						out: {
30704							attr: {
30705								'class': 'redactor-dropdown-link-inactive',
30706								'aria-disabled': true,
30707							}
30708						}
30709					}
30710				};
30711
30712				dropdown.insert_column_left = {
30713					title: this.lang.get('insert-column-left'),
30714					func: this.table.addColumnLeft,
30715					observe: {
30716						element: 'table',
30717						out: {
30718							attr: {
30719								'class': 'redactor-dropdown-link-inactive',
30720								'aria-disabled': true,
30721							}
30722						}
30723					}
30724				};
30725
30726				dropdown.insert_column_right = {
30727					title: this.lang.get('insert-column-right'),
30728					func: this.table.addColumnRight,
30729					observe: {
30730						element: 'table',
30731						out: {
30732							attr: {
30733								'class': 'redactor-dropdown-link-inactive',
30734								'aria-disabled': true,
30735							}
30736						}
30737					}
30738				};
30739
30740				dropdown.add_head = {
30741					title: this.lang.get('add-head'),
30742					func: this.table.addHead,
30743					observe: {
30744						element: 'table',
30745						out: {
30746							attr: {
30747								'class': 'redactor-dropdown-link-inactive',
30748								'aria-disabled': true,
30749							}
30750						}
30751					}
30752				};
30753
30754				dropdown.delete_head = {
30755					title: this.lang.get('delete-head'),
30756					func: this.table.deleteHead,
30757					observe: {
30758						element: 'table',
30759						out: {
30760							attr: {
30761								'class': 'redactor-dropdown-link-inactive',
30762								'aria-disabled': true,
30763							}
30764						}
30765					}
30766				};
30767
30768				dropdown.delete_column = {
30769    				title: this.lang.get('delete-column'),
30770    				func: this.table.deleteColumn,
30771    				observe: {
30772    					element: 'table',
30773    					out: {
30774    						attr: {
30775    							'class': 'redactor-dropdown-link-inactive',
30776    							'aria-disabled': true,
30777    						}
30778    					}
30779    				}
30780    			};
30781
30782				dropdown.delete_row = {
30783					title: this.lang.get('delete-row'),
30784					func: this.table.deleteRow,
30785					observe: {
30786						element: 'table',
30787						out: {
30788							attr: {
30789								'class': 'redactor-dropdown-link-inactive',
30790								'aria-disabled': true,
30791							}
30792						}
30793					}
30794				};
30795
30796				dropdown.delete_table = {
30797					title: this.lang.get('delete-table'),
30798					func: this.table.deleteTable,
30799					observe: {
30800						element: 'table',
30801						out: {
30802							attr: {
30803								'class': 'redactor-dropdown-link-inactive',
30804								'aria-disabled': true,
30805							}
30806						}
30807					}
30808				};
30809
30810
30811				var button = this.button.addBefore('link', 'table', this.lang.get('table'));
30812				this.button.setIcon(button, '<i class="re-icon-table"></i>');
30813				this.button.addDropdown(button, dropdown);
30814			},
30815			insert: function()
30816			{
30817				if (this.table.getTable())
30818				{
30819					return;
30820				}
30821
30822				this.placeholder.hide();
30823
30824				var rows = 2;
30825				var columns = 3;
30826				var $tableBox = $('<div>');
30827				var $table = $('<table />');
30828
30829				for (var i = 0; i < rows; i++)
30830				{
30831					var $row = $('<tr>');
30832
30833					for (var z = 0; z < columns; z++)
30834					{
30835						var $column = $('<td>' + this.opts.invisibleSpace + '</td>');
30836
30837						// set the focus to the first td
30838						if (i === 0 && z === 0)
30839						{
30840							$column.append(this.marker.get());
30841						}
30842
30843						$($row).append($column);
30844					}
30845
30846					$table.append($row);
30847				}
30848
30849				$tableBox.append($table);
30850				var html = $tableBox.html();
30851
30852				this.buffer.set();
30853
30854				var current = this.selection.current();
30855				if ($(current).closest('li', this.core.editor()[0]).length !== 0)
30856				{
30857					$(current).closest('ul, ol').first().after(html);
30858				}
30859				else
30860				{
30861					this.air.collapsed();
30862					this.insert.html(html);
30863				}
30864
30865				this.selection.restore();
30866				this.core.callback('insertedTable', $table);
30867			},
30868			getTable: function()
30869			{
30870				var $table = $(this.selection.current()).closest('table');
30871
30872				if (!this.utils.isRedactorParent($table))
30873				{
30874					return false;
30875				}
30876
30877				if ($table.length === 0)
30878				{
30879					return false;
30880				}
30881
30882				return $table;
30883			},
30884			restoreAfterDelete: function($table)
30885			{
30886				this.selection.restore();
30887				$table.find('span.redactor-selection-marker').remove();
30888
30889			},
30890			deleteTable: function()
30891			{
30892				var $table = this.table.getTable();
30893				if (!$table)
30894				{
30895					return;
30896				}
30897
30898				this.buffer.set();
30899
30900
30901				var $next = $table.next();
30902				if (!this.opts.linebreaks && $next.length !== 0)
30903				{
30904					this.caret.start($next);
30905				}
30906				else
30907				{
30908					this.caret.after($table);
30909				}
30910
30911
30912				$table.remove();
30913
30914
30915			},
30916			deleteRow: function()
30917			{
30918				var $table = this.table.getTable();
30919				if (!$table)
30920				{
30921					return;
30922				}
30923
30924				var $current = $(this.selection.current());
30925
30926				this.buffer.set();
30927
30928				var $current_tr = $current.closest('tr');
30929				var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
30930				if ($focus_tr.length)
30931				{
30932					var $focus_td = $focus_tr.children('td, th').first();
30933					if ($focus_td.length)
30934					{
30935						$focus_td.prepend(this.marker.get());
30936					}
30937				}
30938
30939				$current_tr.remove();
30940				this.table.restoreAfterDelete($table);
30941			},
30942			deleteColumn: function()
30943			{
30944				var $table = this.table.getTable();
30945				if (!$table)
30946				{
30947					return;
30948				}
30949
30950				this.buffer.set();
30951
30952				var $current = $(this.selection.current());
30953				var $current_td = $current.closest('td, th');
30954				var index = $current_td[0].cellIndex;
30955
30956				$table.find('tr').each($.proxy(function(i, elem)
30957				{
30958					var $elem = $(elem);
30959					var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
30960					if (i === 0)
30961					{
30962						$elem.find('td, th').eq(focusIndex).prepend(this.marker.get());
30963					}
30964
30965					$elem.find('td, th').eq(index).remove();
30966
30967				}, this));
30968
30969				this.table.restoreAfterDelete($table);
30970			},
30971			addHead: function()
30972			{
30973				var $table = this.table.getTable();
30974				if (!$table)
30975				{
30976					return;
30977				}
30978
30979				this.buffer.set();
30980
30981				if ($table.find('thead').length !== 0)
30982				{
30983					this.table.deleteHead();
30984					return;
30985				}
30986
30987				var tr = $table.find('tr').first().clone();
30988				tr.find('td').replaceWith($.proxy(function()
30989				{
30990					return $('<th>').html(this.opts.invisibleSpace);
30991				}, this));
30992
30993				$thead = $('<thead></thead>').append(tr);
30994				$table.prepend($thead);
30995
30996
30997
30998			},
30999			deleteHead: function()
31000			{
31001				var $table = this.table.getTable();
31002				if (!$table)
31003				{
31004					return;
31005				}
31006
31007				var $thead = $table.find('thead');
31008				if ($thead.length === 0)
31009				{
31010					return;
31011				}
31012
31013				this.buffer.set();
31014
31015				$thead.remove();
31016
31017			},
31018			addRowAbove: function()
31019			{
31020				this.table.addRow('before');
31021			},
31022			addRowBelow: function()
31023			{
31024				this.table.addRow('after');
31025			},
31026			addColumnLeft: function()
31027			{
31028				this.table.addColumn('before');
31029			},
31030			addColumnRight: function()
31031			{
31032				this.table.addColumn('after');
31033			},
31034			addRow: function(type)
31035			{
31036				var $table = this.table.getTable();
31037				if (!$table)
31038				{
31039					return;
31040				}
31041
31042				this.buffer.set();
31043
31044				var $current = $(this.selection.current());
31045				var $current_tr = $current.closest('tr');
31046				var new_tr = $current_tr.clone();
31047
31048				new_tr.find('th').replaceWith(function()
31049				{
31050					var $td = $('<td>');
31051					$td[0].attributes = this.attributes;
31052
31053					return $td.append($(this).contents());
31054				});
31055
31056				new_tr.find('td').html(this.opts.invisibleSpace);
31057
31058				if (type === 'after')
31059				{
31060					$current_tr.after(new_tr);
31061				}
31062				else
31063				{
31064					$current_tr.before(new_tr);
31065				}
31066
31067
31068			},
31069			addColumn: function (type)
31070			{
31071				var $table = this.table.getTable();
31072				if (!$table)
31073				{
31074					return;
31075				}
31076
31077				var index = 0;
31078				var current = $(this.selection.current());
31079
31080				this.buffer.set();
31081
31082				var $current_tr = current.closest('tr');
31083				var $current_td = current.closest('td, th');
31084
31085				$current_tr.find('td, th').each($.proxy(function(i, elem)
31086				{
31087					if ($(elem)[0] === $current_td[0])
31088					{
31089						index = i;
31090					}
31091
31092				}, this));
31093
31094				$table.find('tr').each($.proxy(function(i, elem)
31095				{
31096					var $current = $(elem).find('td, th').eq(index);
31097
31098					var td = $current.clone();
31099					td.html(this.opts.invisibleSpace);
31100
31101					if (type === 'after')
31102					{
31103						$current.after(td);
31104					}
31105					else
31106					{
31107						$current.before(td);
31108					}
31109
31110				}, this));
31111
31112
31113			}
31114		};
31115	};
31116})(jQuery);
31117(function($)
31118{
31119	$.Redactor.prototype.video = function()
31120	{
31121		return {
31122			reUrlYoutube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
31123			reUrlVimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
31124			langs: {
31125				en: {
31126					"video": "Video",
31127					"video-html-code": "Video Embed Code or Youtube/Vimeo Link"
31128				}
31129			},
31130			getTemplate: function()
31131			{
31132				return String()
31133				+ '<div class="modal-section" id="redactor-modal-video-insert">'
31134					+ '<section>'
31135						+ '<label>' + this.lang.get('video-html-code') + '</label>'
31136						+ '<textarea id="redactor-insert-video-area" style="height: 160px;"></textarea>'
31137					+ '</section>'
31138					+ '<section>'
31139						+ '<button id="redactor-modal-button-action">Insert</button>'
31140						+ '<button id="redactor-modal-button-cancel">Cancel</button>'
31141					+ '</section>'
31142				+ '</div>';
31143			},
31144			init: function()
31145			{
31146				var button = this.button.addAfter('image', 'video', this.lang.get('video'));
31147				this.button.setIcon(button, '<i class="re-icon-video"></i>');
31148				this.button.addCallback(button, this.video.show);
31149			},
31150			show: function()
31151			{
31152				this.modal.addTemplate('video', this.video.getTemplate());
31153
31154				this.modal.load('video', this.lang.get('video'), 700);
31155
31156				// action button
31157				this.modal.getActionButton().text(this.lang.get('insert')).on('click', this.video.insert);
31158				this.modal.show();
31159
31160				// focus
31161				if (this.detect.isDesktop())
31162				{
31163					setTimeout(function()
31164					{
31165						$('#redactor-insert-video-area').focus();
31166
31167					}, 1);
31168				}
31169
31170
31171			},
31172			insert: function()
31173			{
31174				var data = $('#redactor-insert-video-area').val();
31175
31176				if (!data.match(/<iframe|<video/gi))
31177				{
31178					data = this.clean.stripTags(data);
31179
31180					this.opts.videoContainerClass = (typeof this.opts.videoContainerClass === 'undefined') ? 'video-container' : this.opts.videoContainerClass;
31181
31182					// parse if it is link on youtube & vimeo
31183					var iframeStart = '<div class="' + this.opts.videoContainerClass + '"><iframe style="width: 500px; height: 281px;" src="',
31184						iframeEnd = '" frameborder="0" allowfullscreen></iframe></div>';
31185
31186					if (data.match(this.video.reUrlYoutube))
31187					{
31188						data = data.replace(this.video.reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
31189					}
31190					else if (data.match(this.video.reUrlVimeo))
31191					{
31192						data = data.replace(this.video.reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
31193					}
31194				}
31195
31196				this.modal.close();
31197				this.placeholder.hide();
31198
31199				// buffer
31200				this.buffer.set();
31201
31202				// insert
31203				this.air.collapsed();
31204				this.insert.html(data);
31205
31206			}
31207
31208		};
31209	};
31210})(jQuery);
31211
31212function start_search($subdomain, $content_type, $search, $index) {
31213
31214  $('div#menu ul.menu li a').each(function(){
31215    $(this).click(function(e){
31216      //$(this).toggleClass('active');
31217      $('div#menu ul.menu li a').removeClass('active');
31218      $(this).addClass('active');
31219      $search.data('page', '1');
31220      process_search($subdomain, $content_type, $search, $index, get_query(), get_filters());
31221      e.preventDefault();
31222    });
31223  });
31224
31225  process_search($subdomain, $content_type, $search, $index, get_query(), get_filters());
31226  $('input#query').keyup(function(){
31227    $search.data('page', '1');
31228    process_search($subdomain, $content_type, $search, $index, get_query(), get_filters());
31229  });
31230
31231}
31232
31233function get_query() {
31234
31235  return $('input#query').val()
31236
31237}
31238
31239function get_filters() {
31240
31241  $filters = [];
31242  $('div#menu ul.menu li a.active').each(function(){
31243    $filters.push($(this).text());
31244  });
31245  return $filters
31246
31247}
31248
31249function process_search($subdomain, $content_type, $search, $index, $query, $filters) {
31250
31251  $base_filters = 'active=1 AND status=1 AND displayed=1';
31252  if ($content_type == 'Event') {
31253    $current_date = Math.floor((new Date().getTime())/1000);
31254    $base_filters += ' AND start_date>='+$current_date;
31255  }
31256
31257  $index.search($('input#query').val(), { hitsPerPage: parseInt($search.data('rows')), page: (parseInt($search.data('page'))-1), filters: $base_filters, tagFilters: $.merge(['tenant_'+$subdomain], $filters) })
31258    .then(function searchDone($response) {
31259      refresh_search_results($subdomain, $content_type, $search, $index, $query, $filters, $response);
31260    })
31261    .catch(function searchFailure($response) {
31262      console.error($response);
31263    });
31264
31265}
31266
31267function refresh_search_results($subdomain, $content_type, $search, $index, $query, $filters, $response) {
31268
31269  $('div#results', $search).html('');
31270  $('div.pagination', $search).remove();
31271  if ($response.hits.length > 0) {
31272    if ($response.nbPages > 1) {
31273      $pagination = $('<div class="pagination clearfix"></div>');
31274      $back_span = $('<span class="back"></span>');
31275      if ($response.page > 0) {
31276        $back_link = $('<a href="#" class="back button"><span class="mdi mdi-chevron-left"></span>Back</a>');
31277        $back_link.click(function(e){
31278          $search.data('page', (parseInt($search.data('page')) - 1));
31279          process_search($subdomain, $content_type, $search, $index, $query, $filters);
31280          $(window).scrollTop($('div#main').offset().top);
31281          e.preventDefault();
31282        });
31283        $back_span.append($back_link);
31284      }
31285      $pagination.append($back_span);
31286      $pagination.append($('<span class="count">Page '+($response.page+1)+' of '+$response.nbPages+'</span>'));
31287      $next_span = $('<span class="next"></span>');
31288      if (($response.page+1) < $response.nbPages) {
31289        $next_link = $('<a href="#" class="next button">Next<span class="mdi mdi-chevron-right"></span></a>');
31290        $next_link.click(function(e){
31291          $search.data('page', (parseInt($search.data('page')) + 1));
31292          process_search($subdomain, $content_type, $search, $index, $query, $filters);
31293          $(window).scrollTop($('div#main').offset().top);
31294          e.preventDefault();
31295        });
31296        $next_span.append($next_link);
31297      }
31298      $pagination.append($next_span);
31299      $search.append($pagination);
31300    }
31301    $.each($response.hits, function($key, $record) {
31302      $('div#results', $search).append('<div class="panel">'+$record.teaser+'</div>')
31303    });
31304  } else {
31305    $('div#results', $search).append('<div class="panel"><strong>There are no '+$content_type.toLowerCase()+'s to display.</strong></div>')
31306  }
31307
31308};
31309
31310
31311function start_segments() {
31312
31313  $('table.segments tr.filter').each(function(){
31314    $('td.field label', $(this)).text(($('td.field select', $(this)).val() != '') ? $('td.field select option:selected', $(this)).parent().attr('label') : ' ');
31315    refresh_selects($(this));
31316    $('td.field select', $(this)).change(function(){
31317      refresh_filter($(this).parents('tr.filter'));
31318    });
31319    $('td.actions input', $(this)).change(function(){
31320      refresh_filter_delete($(this).parents('tr.filter'));
31321    });
31322  });
31323
31324  $('a.add_filter').each(function(){
31325    attach_add_filter($(this));
31326  });
31327
31328  $('a.add_filterset').click(function($event){
31329    time = new Date().getTime();
31330    regexp = new RegExp($(this).data('id'), 'g')
31331    $('div#filtersets').append($(this).data('fields').replace(regexp, time));
31332    $('a.add_filter').each(function(){
31333      attach_add_filter($(this));
31334    });
31335    $filter = $('div#filtersets tr.filter:last');
31336    refresh_filter($filter);
31337    $('td.field select', $filter).change(function(){
31338      refresh_filter($(this).parents('tr.filter'));
31339    });
31340    $('td.actions span.delete', $filter).click(function($event){
31341      delete_filter($(this).parents('tr.filter'));
31342      $event.preventDefault();
31343    });
31344    $event.preventDefault();
31345  });
31346
31347  $('td.actions span.delete').click(function($event){
31348    delete_filter($(this).parents('tr.filter'));
31349    $event.preventDefault();
31350  });
31351
31352}
31353
31354function attach_add_filter($link) {
31355
31356  $link.click(function($event){
31357    time = new Date().getTime();
31358    regexp = new RegExp($(this).data('id'), 'g')
31359    $('table.segments tbody.filterset', $(this).parents('div.box')).append($(this).data('fields').replace(regexp, time));
31360    $filter = $('table.segments tr.filter:last', $(this).parents('div.box'));
31361    refresh_filter($filter);
31362    $('td.field select', $filter).change(function(){
31363      refresh_filter($(this).parents('tr.filter'));
31364    });
31365    $('td.actions span.delete', $filter).click(function($event){
31366      delete_filter($(this).parents('tr.filter'));
31367      $event.preventDefault();
31368    });
31369    $event.preventDefault();
31370  });
31371
31372}
31373
31374function refresh_filter($filter) {
31375
31376  $('td.field label', $filter).text(($('td.field select', $filter).val() != '') ? $('td.field select option:selected', $filter).parent().attr('label') : ' ');
31377  $('td.match select', $filter).hide();
31378  $('td.value input', $filter).hide();
31379  $('td.value input', $filter).val('');
31380  $('td.value select', $filter).remove();
31381  refresh_filter_delete($filter);
31382  if ($('td.field select', $filter).val() != '') {
31383    switch ($('td.field option:selected', $filter).attr('class')) {
31384      case 'text':
31385        $match_options = [
31386          ['contains', 'contains'],
31387          ['is exactly', 'exact'],
31388          ['starts with', 'starts'],
31389          ['ends with', 'ends'],
31390          ['is present', 'present'],
31391          ['is absent', 'absent']
31392        ];
31393        refresh_selects($filter);
31394        break;
31395      case 'date':
31396        $match_options = [
31397          ['is on or before', 'before'],
31398          ['is on or after', 'after']
31399        ];
31400        refresh_selects($filter);
31401        break;
31402      case 'date_relative':
31403        $match_options = [
31404          ['is in the next', 'next'],
31405          ['is in the past', 'past']
31406        ];
31407        refresh_selects($filter);
31408        break;
31409      case 'options':
31410        $match_options = [
31411          ['is', 'is'],
31412          ['is not', 'is_not']
31413        ];
31414        refresh_selects($filter);
31415        break;
31416      case 'association':
31417        $match_options = [
31418          ['is', 'is'],
31419          ['is not', 'is_not']
31420        ];
31421        refresh_selects($filter);
31422        break;
31423      case 'field-text':
31424        $match_options = [
31425          ['contains', 'contains'],
31426          ['is exactly', 'exact'],
31427          ['starts with', 'starts'],
31428          ['ends with', 'ends'],
31429          ['is present', 'present'],
31430          ['is absent', 'absent']
31431        ];
31432        refresh_selects($filter);
31433        break;
31434      case 'field-textarea':
31435        $match_options = [
31436          ['contains', 'contains'],
31437          ['is exactly', 'exact'],
31438          ['starts with', 'starts'],
31439          ['ends with', 'ends'],
31440          ['is present', 'present'],
31441          ['is absent', 'absent']
31442        ];
31443        refresh_selects($filter);
31444        break;
31445      case 'field-date':
31446        $match_options = [
31447          ['is on or before', 'before'],
31448          ['is on or after', 'after'],
31449          ['is present', 'present'],
31450          ['is absent', 'absent']
31451        ];
31452        refresh_selects($filter);
31453        break;
31454      case 'field-file':
31455        $match_options = [
31456          ['is present', 'present'],
31457          ['is absent', 'absent']
31458        ];
31459        refresh_selects($filter);
31460        break;
31461      case 'field-select':
31462        $match_options = [
31463          ['is', 'is'],
31464          ['is not', 'is_not']
31465        ];
31466        refresh_selects($filter);
31467        break;
31468      case 'field-radios':
31469        $match_options = [
31470          ['is', 'is'],
31471          ['is not', 'is_not']
31472        ];
31473        refresh_selects($filter);
31474        break;
31475      case 'field-checkboxes':
31476        $match_options = [
31477          ['is', 'is'],
31478          ['is not', 'is_not']
31479        ];
31480        refresh_selects($filter);
31481        break;
31482      default:
31483        $match_options = [];
31484        break;
31485    }
31486    $('td.match select option', $filter).remove();
31487    $.each($match_options, function($index, $option){
31488      $('td.match select', $filter).append('<option value="'+$option[1]+'">'+$option[0]+'</option>')
31489    });
31490    $('td.match select', $filter).show();
31491  }
31492
31493}
31494
31495function refresh_selects($filter) {
31496
31497  $field_select = $('td.field select', $filter);
31498  $match_select = $('td.match select', $filter);
31499  $match_select.hide();
31500  $match_select.off('change');
31501  if ($field_select.val() != '') {
31502    $match_select.show();
31503  }
31504  $input = $('td.value input', $filter);
31505  $input.hide();
31506  switch ($('option:selected', $field_select).attr('class')) {
31507    case 'text':
31508      $input.attr('type', 'text');
31509      $input.show();
31510      break;
31511    case 'date':
31512      $input.attr('type', 'date');
31513      $input.show();
31514      break;
31515    case 'date_relative':
31516      $value_options = [];
31517      switch ($('td.field select', $filter).val()) {
31518        case 'booking_date_relative':
31519          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31520          break;
31521        case 'membership_created_relative':
31522          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31523          break;
31524        case 'membership_next_charge_relative':
31525          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31526          break;
31527        case 'relationship_membership_created_relative':
31528          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31529          break;
31530        case 'relationship_membership_next_charge_relative':
31531          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31532          break;
31533        case 'affiliation_membership_created_relative':
31534          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31535          break;
31536        case 'affiliation_membership_next_charge_relative':
31537          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31538          break;
31539        case 'organisation_membership_created_relative':
31540          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31541          break;
31542        case 'organisation_membership_next_charge_relative':
31543          $value_options = [["1 day", "1_day"],["3 days", "3_days"],["1 week", "1_week"],["2 weeks", "2_weeks"],["1 month", "1_month"],["3 months", "3_months"],["6 months", "6_months"],["1 year", "1_year"]]
31544          break;
31545      }
31546      $select = create_select($value_options, $input.val());
31547      $select.attr('name', $input.attr('name'));
31548      $input.after($select);
31549      break;
31550    case 'options':
31551      $value_options = [];
31552      switch ($('td.field select', $filter).val()) {
31553        case 'address_country':
31554          $value_options = [["- Select country -", ""], ["United Kingdom", "GB"], ["Afghanistan", "AF"], ["Aland Islands", "AX"], ["Albania", "AL"], ["Algeria", "DZ"], ["American Samoa", "AS"], ["Andorra", "AD"], ["Angola", "AO"], ["Anguilla", "AI"], ["Antarctica", "AQ"], ["Antigua and Barbuda", "AG"], ["Argentina", "AR"], ["Armenia", "AM"], ["Aruba", "AW"], ["Australia", "AU"], ["Austr
31554ia", "AT"], ["Azerbaijan", "AZ"], ["Bahamas", "BS"], ["Bahrain", "BH"], ["Bangladesh", "BD"], ["Barbados", "BB"], ["Belarus", "BY"], ["Belgium", "BE"], ["Belize", "BZ"], ["Benin", "BJ"], ["Bermuda", "BM"], ["Bhutan", "BT"], ["Bolivia (Plurinational State of)", "BO"], ["Bonaire, Sint Eustatius and Saba", "BQ"], ["Bosnia and Herzegovina", "BA"], ["Botswana", "BW"], ["Bouvet Island", "BV"], ["Brazil", "BR"], ["British Indian Ocean Territory", "IO"], ["Brunei Darussalam", "BN"], ["Bulgaria", "BG"], ["Burkina Faso", "BF"], ["Burundi", "BI"], ["Cabo Verde", "CV"], ["Cambodia", "KH"], ["Cameroon", "CM"], ["Canada", "CA"], ["Cayman Islands", "KY"], ["Central African Republic", "CF"], ["Chad", "TD"], ["Chile", "CL"], ["China", "CN"], ["Christmas Island", "CX"], ["Cocos (Keeling) Islands", "CC"], ["Colombia", "CO"], ["Comoros", "KM"], ["Congo", "CG"], ["Congo (Democratic Republic of the)", "CD"], ["Cook Islands", "CK"], ["Costa Rica", "CR"], ["Croatia", "HR"], ["Cuba", "CU"], ["Curaçao", "CW"], ["Cyprus", "CY"], ["Czechia", "CZ"], ["Côte d'Ivoire", "CI"], ["Denmark", "DK"], ["Djibouti", "DJ"], ["Dominica", "DM"], ["Dominican Republic", "DO"], ["Ecuador", "EC"], ["Egypt", "EG"], ["El Salvador", "SV"], ["Equatorial Guinea", "GQ"], ["Eritrea", "ER"], ["Estonia", "EE"], ["Eswatini", "SZ"], ["Ethiopia", "ET"], ["Falkland Islands (Malvinas)", "FK"], ["Faroe Islands", "FO"], ["Fiji", "FJ"], ["Finland", "FI"], ["France", "FR"], ["French Guiana", "GF"], ["French Polynesia", "PF"], ["French Southern Territories", "TF"], ["Gabon", "GA"], ["Gambia", "GM"], ["Georgia", "GE"], ["Germany", "DE"], ["Ghana", "GH"], ["Gibraltar", "GI"], ["Greece", "GR"], ["Greenland", "GL"], ["Grenada", "GD"], ["Guadeloupe", "GP"], ["Guam", "GU"], ["Guatemala", "GT"], ["Guernsey", "GG"], ["Guinea", "GN"], ["Guinea-Bissau", "GW"], ["Guyana", "GY"], ["Haiti", "HT"], ["Heard Island and McDonald Islands", "HM"], ["Holy See", "VA"], ["Honduras", "HN"], ["Hong Kong", "HK"], ["Hungary", "HU"], ["Iceland", "IS"], ["India", "IN"], ["Indonesia", "ID"], ["Iran (Islamic Republic of)", "IR"], ["Iraq", "IQ"], ["Ireland", "IE"], ["Isle of Man", "IM"], ["Israel", "IL"], ["Italy", "IT"], ["Jamaica", "JM"], ["Japan", "JP"], ["Jersey", "JE"], ["Jordan", "JO"], ["Kazakhstan", "KZ"], ["Kenya", "KE"], ["Kiribati", "KI"], ["Korea (Democratic People's Republic of)", "KP"], ["Korea (Republic of)", "KR"], ["Kuwait", "KW"], ["Kyrgyzstan", "KG"], ["Lao People's Democratic Republic", "LA"], ["Latvia", "LV"], ["Lebanon", "LB"], ["Lesotho", "LS"], ["Liberia", "LR"], ["Libya", "LY"], ["Liechtenstein", "LI"], ["Lithuania", "LT"], ["Luxembourg", "LU"], ["Macao", "MO"], ["Madagascar", "MG"], ["Malawi", "MW"], ["Malaysia", "MY"], ["Maldives", "MV"], ["Mali", "ML"], ["Malta", "MT"], ["Marshall Islands", "MH"], ["Martinique", "MQ"], ["Mauritania", "MR"], ["Mauritius", "MU"], ["Mayotte", "YT"], ["Mexico", "MX"], ["Micronesia (Federated States of)", "FM"], ["Moldova (Republic of)", "MD"], ["Monaco", "MC"], ["Mongolia", "MN"], ["Montenegro", "ME"], ["Montserrat", "MS"], ["Morocco", "MA"], ["Mozambique", "MZ"], ["Myanmar", "MM"], ["Namibia", "NA"], ["Nauru", "NR"], ["Nepal", "NP"], ["Netherlands", "NL"], ["New Caledonia", "NC"], ["New Zealand", "NZ"], ["Nicaragua", "NI"], ["Niger", "
31554NE"], ["Nigeria", "NG"], ["Niue", "NU"], ["Norfolk Island", "NF"], ["North Macedonia", "MK"], ["Northern Mariana Islands", "MP"], ["Norway", "NO"], ["Oman", "OM"], ["Pakistan", "PK"], ["Palau", "PW"], ["Palestine, State of", "PS"], ["Panama", "PA"], ["Papua New Guinea", "PG"], ["Paraguay", "PY"], ["Peru", "PE"], ["Philippines", "PH"], ["Pitcairn", "PN"], ["Poland", "PL"], ["Portugal", "PT"], ["Puerto Rico", "PR"], ["Qatar", "QA"], ["Romania", "RO"], ["Russian Federation", "RU"], ["Rwanda", "RW"], ["Réunion", "RE"], ["Saint Barthélemy", "BL"], ["Saint Helena, Ascension and Tristan da Cunha", "SH"], ["Saint Kitts and Nevis", "KN"], ["Saint Lucia", "LC"], ["Saint Martin (French part)", "MF"], ["Saint Pierre and Miquelon", "PM"], ["Saint Vincent and the Grenadines", "VC"], ["Samoa", "WS"], ["San Marino", "SM"], ["Sao Tome and Principe", "ST"], ["Saudi Arabia", "SA"], ["Senegal", "SN"], ["Serbia", "RS"], ["Seychelles", "SC"], ["Sierra Leone", "SL"], ["Singapore", "SG"], ["Sint Maarten (Dutch part)", "SX"], ["Slovakia", "SK"], ["Slovenia", "SI"], ["Solomon Islands", "SB"], ["Somalia", "SO"], ["South Africa", "ZA"], ["South Georgia", "GS"], ["South Sudan", "SS"], ["Spain", "ES"], ["Sri Lanka", "LK"], ["Sudan", "SD"], ["Suriname", "SR"], ["Svalbard and Jan Mayen", "SJ"], ["Swede
31554n", "SE"], ["Switzerland", "CH"], ["Syrian Arab Republic", "SY"], ["Taiwan", "TW"], ["Tajikistan", "TJ"], ["Tanzania, United Republic of", "TZ"], ["Thailand", "TH"], ["Timor-Leste", "TL"], ["Togo", "TG"], ["Tokelau", "TK"], ["Tonga", "TO"], ["Trinidad and Tobago", "TT"], ["Tunisia", "TN"], ["Turkmenistan", "TM"], ["Turks and Caicos Islands", "TC"], ["Tuvalu", "TV"], ["Türkiye", "TR"], ["Uganda", "UG"], ["Ukraine", "UA"], ["United Arab Emirates", "AE"], ["United Kingdom", "GB"], ["United States Minor Outlying Islands", "UM"], ["United States of America", "US"], ["Uruguay", "UY"], ["Uzbekistan", "UZ"], ["Vanuatu", "VU"], ["Venezuela (Bolivarian Republic of)", "VE"], ["Viet Nam", "VN"], ["Virgin Islands (British)", "VG"], ["Virgin Islands (U.S.)", "VI"], ["Wallis and Futuna", "WF"], ["Western Sahara", "EH"], ["Yemen", "YE"], ["Zambia", "ZM"], ["Zimbabwe", "ZW"]]
31555          break;
31556        case 'directory':
31557          $value_options = [["Active", "active"]]
31558          break;
31559        case 'gift_aid':
31560          $value_options = [["Active", "active"]]
31561          break;
31562        case 'membership_status':
31563          $value_options = [["Valid", "valid"],["Pending","pending"],["Approved","approved"],["Overdue", "overdue"],["Expired", "expired"],["Incomplete", "incomplete"],["Cancelled", "cancelled"]]
31564          break;
31565        case 'membership_payment_method':
31566          $value_options = [["Card (repeat)", "card_repeat"],["Direct Debit", "mandate"],["Manual", "manual"]]
31567          break;
31568        case 'relationship_membership_status':
31569          $value_options = [["Valid", "valid"],["Pending","pending"],["Approved","approved"],["Overdue", "overdue"],["Expired", "expired"],["Incomplete", "incomplete"],["Cancelled", "cancelled"]]
31570          break;
31571        case 'relationship_membership_payment_method':
31572          $value_options = [["Card (repeat)", "card_repeat"],["Direct Debit", "mandate"],["Manual", "manual"]]
31573          break;
31574        case 'affiliation_membership_status':
31575          $value_options = [["Valid", "valid"],["Pending","pending"],["Approved","approved"],["Overdue", "overdue"],["Expired", "expired"],["Incomplete", "incomplete"],["Cancelled", "cancelled"]]
31576          break;
31577        case 'affiliation_membership_payment_method':
31578          $value_options = [["Direct Debit", "mandate"],["Manual", "manual"]]
31579          break;
31580        case 'organisation_membership_status':
31581          $value_options = [["Valid", "valid"],["Pending","pending"],["Approved","approved"],["Overdue", "overdue"],["Expired", "expired"],["Incomplete", "incomplete"],["Cancelled", "cancelled"]]
31582          break;
31583        case 'organisation_membership_payment_method':
31584          $value_options = [["Direct Debit", "mandate"],["Manual", "manual"]]
31585          break;
31586        case 'affiliation_type':
31587          $value_options = [["Any", "any"],["Key person", "key_person"]]
31588          break;
31589        case 'role':
31590          $value_options = [["Editor", "Editor"],["Administrator", "Administrator"]]
31591          break;
31592        case 'user':
31593          $value_options = [["Any", "any"],["Active", "active"]]
31594          break;
31595      }
31596      $select = create_select($value_options, $input.val());
31597      $select.attr('name', $input.attr('name'));
31598      $input.after($select);
31599      break;
31600    case 'association':
31601      switch ($('td.field select', $filter).val()) {
31602        case 'consent_type':
31603          $.ajax({
31604            type: 'GET',
31605            url: '/admin/consent_types',
31606            context: $input,
31607            dataType: 'json',
31608            success: function($results) {
31609              $value = $(this).val();
31610              $select = $('<select></select>');
31611              $select.append('<option value="any">Any</option>');
31612              $.each($results, function($index, $result){
31613                $option = '<option value="'+$result.id+'"'
31614                if ($value == $result.id) {
31615                  $option += ' selected="selected"'
31616                }
31617                $option += '>'+$result.label+'</option>'
31618                $select.append($option);
31619              });
31620              $input = $('input', $(this).parents('td.value'));
31621              $select.attr('name', $input.attr('name'));
31622              $input.after($select);
31623            }
31624          });
31625          break;
31626        case 'event':
31627          $.ajax({
31628            type: 'GET',
31629            url: '/admin/events',
31630            context: $input,
31631            dataType: 'json',
31632            success: function($results) {
31633              $value = $(this).val();
31634              $select = $('<select></select>');
31635              $select.append('<option value="any">Any</option>');
31636              $.each($results, function($index, $result){
31637                $option = '<option value="'+$result.id+'"'
31638                if ($value == $result.id) {
31639                  $option += ' selected="selected"'
31640                }
31641                $option += '>'+$result.title+'</option>'
31642                $select.append($option);
31643              });
31644              $input = $('input', $(this).parents('td.value'));
31645              $select.attr('name', $input.attr('name'));
31646              $input.after($select);
31647            }
31648          });
31649          break;
31650        case 'group':
31651          $.ajax({
31652            type: 'GET',
31653            url: '/groups',
31654            context: $input,
31655            dataType: 'json',
31656            success: function($results) {
31657              $value = $(this).val();
31658              $select = $('<select></select>');
31659              $select.append('<option value="any">Any</option>');
31660              $.each($results, function($index, $result){
31661                $option = '<option value="'+$result.id+'"'
31662                if ($value == $result.id) {
31663                  $option += ' selected="selected"'
31664                }
31665                $option += '>
31665'+$result.name+'</option>'
31666                $select.append($option);
31667              });
31668              $input = $('input', $(this).parents('td.value'));
31669              $select.attr('name', $input.attr('name'));
31670              $input.after($select);
31671            }
31672          });
31673          break;
31674        case 'membership_type':
31675          $.ajax({
31676            type: 'GET',
31677            url: '/admin/membership_plans',
31678            context: $input,
31679            data: {
31680              scope: {
31681                type: ['PersonMembershipPlan', 'JointMembershipPlan', 'ProxyMembershipPlan']
31682              }
31683            },
31684            dataType: 'json',
31685            success: function($results) {
31686              $value = $(this).val();
31687              $select = $('<select></select>');
31688              $select.append('<option value="any">Any</option>');
31689              $.each($results, function($index, $result){
31690                $option = '<option value="'+$result.id+'"'
31691                if ($value == $result.id) {
31692                  $option += ' selected="selected"'
31693                }
31694                $option += '>'+$result.name+'</option>'
31695                $select.append($option);
31696              });
31697              $input = $('input', $(this).parents('td.value'));
31698              $select.attr('name', $input.attr('name'));
31699              $input.after($select);
31700            }
31701          });
31702          break;
31703        case 'relationship_membership_type':
31704          $.ajax({
31705            type: 'GET',
31706            url: '/admin/membership_plans',
31707            context: $input,
31708            data: {
31709              scope: {
31710                type: ['JointMembershipPlan', 'ProxyMembershipPlan']
31711              }
31712            },
31713            dataType: 'json',
31714            success: function($results) {
31715              $value = $(this).val();
31716              $select = $('<select></select>');
31717              $select.append('<option value="any">Any</option>');
31718              $.each($results, function($index, $result){
31719                $option = '<option value="'+$result.id+'"'
31720                if ($value == $result.id) {
31721                  $option += ' selected="selected"'
31722                }
31723                $option += '>'+$result.name+'</option>'
31724                $select.append($option);
31725              });
31726              $input = $('input', $(this).parents('td.value'));
31727              $select.attr('name', $input.attr('name'));
31728              $input.after($select);
31729            }
31730          });
31731          break;
31732        case 'affiliation_membership_type':
31733        case 'organisation_membership_type':
31734          $.ajax({
31735            type: 'GET',
31736            url: '/organisation_membership_plans',
31737            context: $input,
31738            dataType: 'json',
31739            success: function($results) {
31740              $value = $(this).val();
31741              $select = $('<select></select>');
31742              $select.append('<option value="any">Any</option>');
31743              $.each($results, function($index, $result){
31744                $option = '<option value="'+$result.id+'"'
31745                if ($value == $result.id) {
31746                  $option += ' selected="selected"'
31747                }
31748                $option += '>'+$result.name+'</option>'
31749                $select.append($option);
31750              });
31751              $input = $('input', $(this).parents('td.value'));
31752              $select.attr('name', $input.attr('name'));
31753              $input.after($select);
31754            }
31755          });
31756          break;
31757      }
31758      break;
31759    case 'field-text':
31760      $input.attr('type', 'text');
31761      if (($match_select.val() != 'present') && ($match_select.val() != 'absent')) {
31762        $input.show();
31763      }
31764      $match_select.change(function(){
31765        $filter = $(this).parents('tr.filter')
31766        $input = $('td.value input', $filter);
31767        if (($(this).val() != 'present') && ($(this).val() != 'absent')) {
31768          $input.show();
31769        } else {
31770          $input.hide();
31771        }
31772      });
31773      break;
31774    case 'field-textarea':
31775      $input.attr('type', 'text');
31776      if (($match_select.val() != 'present') && ($match_select.val() != 'absent')) {
31777        $input.show();
31778      }
31779      $match_select.change(function(){
31780        $filter = $(this).parents('tr.filter')
31781        $input = $('td.value input', $filter);
31782        if (($(this).val() != 'present') && ($(this).val() != 'absent')) {
31783          $input.show();
31784        } else {
31785          $input.hide();
31786        }
31787      });
31788      break;
31789    case 'field-date':
31790      $input.attr('type', 'date');
31791      if (($match_select.val() != 'present') && ($match_select.val() != 'absent')) {
31792        $input.show();
31793      }
31794      $match_select.change(function(){
31795        $filter = $(this).parents('tr.filter')
31796        $input = $('td.value input', $filter);
31797        if (($(this).val() != 'present') && ($(this).val() != 'absent')) {
31798          $input.show();
31799        } else {
31800          $input.hide();
31801        }
31802      });
31803      break;
31804    case 'field-file':
31805      $input.hide();
31806      break;
31807    case 'field-select':
31808      $field_id = $('td.field option:selected', $filter).val().replace('field-','');
31809      $.ajax({
31810        type: 'GET',
31811        url: '/field_options/for/'+$field_id,
31812        context: $input,
31813        dataType: 'json',
31814        success: function($results) {
31815          $value = $(this).val();
31816          $select = $('<select></select>');
31817          $select.append('<option value="any">Any</option>');
31818          $.each($results, function($index, $result){
31819            $option = '<option value="'+$result.id+'"'
31820            if ($value == $result.id) {
31821              $option += ' selected="selected"'
31822            }
31823            $option += '>'+$result.option+'</option>'
31824            $select.append($option);
31825          });
31826          $input = $('input', $(this).parents('td.value'));
31827          $select.attr('name', $input.attr('name'));
31828          $input.after($select);
31829        }
31830      });
31831      break;
31832    case 'field-radios':
31833      $field_id = $('td.field option:selected', $filter).val().replace('field-','');
31834      $.ajax({
31835        type: 'GET',
31836        url: '/field_options/for/'+$field_id,
31837        context: $input,
31838        dataType: 'json',
31839        success: function($results) {
31840          $value = $(this).val();
31841          $select = $('<select></select>');
31842          $select.append('<option value="any">Any</option>');
31843          $.each($results, function($index, $result){
31844            $option = '<option value="'+$result.id+'"'
31845            if ($value == $result.id) {
31846              $option += ' selected="selected"'
31847            }
31848            $option += '>'+$result.option+'</option>'
31849            $select.append($option);
31850          });
31851          $input = $('input', $(this).parents('td.value'));
31852          $select.attr('name', $input.attr('name'));
31853          $input.after($select);
31854        }
31855      });
31856      break;
31857    case 'field-checkboxes':
31858      $field_id = $('td.field option:selected', $filter).val().replace('field-','');
31859      $.ajax({
31860        type: 'GET',
31861        url: '/field_options/for/'+$field_id,
31862        context: $input,
31863        dataType: 'json',
31864        success: function($results) {
31865          $value = $(this).val();
31866          $select = $('<select></select>');
31867          $select.append('<option value="any">Any</option>');
31868          $.each($results, function($index, $result){
31869            $option = '<option value="'+$result.id+'"'
31870            if ($value == $result.id) {
31871              $option += ' selected="selected"'
31872            }
31873            $option += '>'+$result.option+'</option>'
31874            $select.append($option);
31875          });
31876          $input = $('input', $(this).parents('td.value'));
31877          $select.attr('name', $input.attr('name'));
31878          $input.after($select);
31879        }
31880      });
31881      break;
31882  }
31883
31884}
31885
31886function create_select($options, $value) {
31887
31888  $select = $('<select></select>');
31889  $.each($options, function($index, $result){
31890    $option = '<option value="'+$result[1]+'"'
31891    if ($value == $result[1]) {
31892      $option += ' selected="selected"'
31893    }
31894    $option += '>'+$result[0]+'</option>'
31895    $select.append($option);
31896  });
31897  return $select
31898
31899}
31900
31901function refresh_filter_delete($filter) {
31902
31903  $('input.delete', $filter).each(function(){
31904    if ($(this).is(':checked')) {
31905      $filter = $(this).parents('tr.filter');
31906      $table = $filter.parents('table.segments');
31907      if ($filter.prevAll('tr').first().hasClass('logic')) {
31908        $filter.prevAll('tr').first().remove();
31909      } else if ($filter.nextAll('tr').first().hasClass('logic')) {
31910        $filter.nextAll('tr').first().remove();
31911      }
31912      $filter.hide();
31913      if ($('tr.filter:visible', $table).length == 0) {
31914        if ($table.parents('div.box').prev().hasClass('logic')) {
31915          $table.parents('div.box').prev().remove();
31916        } else if ($table.parents('div.box').next().hasClass('logic')) {
31917          $table.parents('div.box').next().remove();
31918        }
31919        $table.parents('div.box').hide();
31920      }
31921//      $('td select', $(this).parents('tr.filter')).css('opacity', '0.2');
31922//      $('td input[type=text]', $(this).parents('tr')).css('opacity', '0.2');
31923//    } else {
31924//      $('td select', $(this).parents('tr.filter')).css('opacity', '1');
31925//      $('td input[type=text]', $(this).parents('tr')).css('opacity', '1');
31926    }
31927  });
31928
31929}
31930
31931function delete_filter($filter) {
31932
31933  $table = $filter.parents('table.segments');
31934  if ($filter.prev().hasClass('logic')) {
31935    $filter.prev().remove();
31936  } else if ($filter.next().hasClass('logic')) {
31937    $filter.next().remove();
31938  }
31939  $filter.remove();
31940  if ($('tr.filter:visible', $table).length == 0) {
31941    if ($table.parents('div.box').prev().hasClass('logic')) {
31942      $table.parents('div.box').prev().remove();
31943    } else if ($table.parents('div.box').next().hasClass('logic')) {
31944      $table.parents('div.box').next().remove();
31945    }
31946    $table.parents('div.box').hide();
31947  }
31948
31949};
31950function fetchSubscription(subscription_hash_id) {
31951  return $.ajax({
31952    type: 'GET',
31953    url: '/subscriptions/'+subscription_hash_id+'/intend',
31954    dataType: 'json'
31955  });
31956};
31957
31958function start_table($table) {
31959
31960  $('table.ajax').each(function(index){
31961    $table = $(this);
31962    $input_query = $('input.query', $table.parents('div.data'));
31963    $form_search = $('form.search', $table.parents('div.data'));
31964    $form_search.submit(function(e){
31965      $params = new URLSearchParams(window.location.search);
31966      $params.delete('search');
31967      if ($input_query.val() != '') {
31968        $params.set('search', $input_query.val());
31969      }
31970      $table.data('search', $input_query.val());
31971      window.history.replaceState({}, '', window.location.pathname+'?'+$params);
31972      reset_export_links();
31973      refresh_table_rows($table)
31974      e.preventDefault();
31975    });
31976    $('div.filter select', $table.parents('div.data')).change(function(){
31977      $filter = $table.data('filter');
31978      delete $filter[$(this).attr('class')];
31979      $params = new URLSearchParams(window.location.search);
31980      $params.delete('filter['+$(this).attr('class')+']');
31981      if ($(this).val() != '') {
31982        $filter[$(this).attr('class')] = $(this).val();
31983        $params.set('filter['+$(this).attr('class')+']', $(this).val());
31984      }
31985      $table.data('filter', $filter);
31986      window.history.replaceState({}, '', window.location.pathname+'?'+$params);
31987      reset_export_links();
31988      refresh_table_rows($table)
31989    });
31990    $('div.filter input[type=date]', $table.parents('div.data')).change(function(){
31991      $filter = $table.data('filter');
31992      delete $filter[$(this).attr('class')];
31993      $params = new URLSearchParams(window.location.search);
31994      $params.delete('filter['+$(this).attr('class')+']');
31995      if ($(this).val() != '') {
31996        $filter[$(this).attr('class')] = $(this).val();
31997        $params.set('filter['+$(this).attr('class')+']', $(this).val());
31998      }
31999      $table.data('filter', $filter);
32000      window.history.replaceState({}, '', window.location.pathname+'?'+$params);
32001      reset_export_links();
32002      refresh_table_rows($table)
32003    });
32004    $('th.sortable', $table).click(function(){
32005      sort = {}
32006      if ($(this).hasClass('asc')) {
32007        $(this).removeClass('asc');
32008        $(this).addClass('desc');
32009        sort[$(this).data('key')] = 'desc';
32010      } else {
32011        $('th.sortable', $(this).parents('table.ajax')).removeClass('asc');
32012        $('th.sortable', $(this).parents('table.ajax')).removeClass('desc');
32013        $(this).addClass('asc');
32014        sort[$(this).data('key')] = 'asc';
32015      }
32016      $table.data('sort', sort);
32017      $table.data('start', 1);
32018      refresh_table_rows($table);
32019    });
32020    refresh_table_rows($(this))
32021  });
32022
32023  $('table.weight').each(function(){
32024    $('select[id$="weight"]', $(this)).each(function(index){
32025      $(this).val($(this).parents('tr').index());
32026    });
32027  });
32028
32029  if ($($table).hasClass('admin')) {
32030    start_table_rowclick($table);
32031    start_table_head_operations($table);
32032    start_table_data_operations($table);
32033    start_table_actionsmenu($table);
32034  };
32035
32036}
32037
32038function refresh_table_rows($table) {
32039
32040  $('tbody', $table).addClass('disable');
32041
32042  var $outline_request = $.ajax({});
32043  var $records_request = $.ajax({});
32044
32045  $outline_request.abort();
32046  $records_request.abort();
32047
32048  $outline_request = $.ajax({
32049    dataType: 'json',
32050    data: {
32051      scope: $table.data('scope'),
32052      filter: $table.data('filter'),
32053      search: $table.data('search')
32054    },
32055    cache: false,
32056    url: $table.data('source')+'/count',
32057    success: $.proxy(function($outline) {
32058
32059      $('div.orientation', $table.parents('div.data')).html('');
32060      if ($outline.count > 0) {
32061        $table.data('result_ids', $outline.pluck_ids)
32062        if ($outline.count <= $table.data('limit')) {
32063          if ($outline.count > 0) {
32064            $('div.orientation', $table.parents('div.data')).show();
32065            $pagecount = Math.ceil($outline.count / $table.data('limit'))
32066  //          $total = $('<div class="total">'+$table.data('start')+' to '+((($table.data('start')+$table.data('limit')-1)<$outline.count) ? ($table.data('start')+$table.data('limit')-1) : $outline.count)+' of '+$outline.count+' items</div>');
32067            $pagination = $('<div class="pagination"></div>');
32068            $pagination.append('<span class="spacer">'+$table.data('start')+' to '+((($table.data('start')+$table.data('limit')-1)<$outline.count) ? ($table.data('start')+$table.data('limit')-1) : $outline.count)+' of '+$outline.count+'<
32068/span>');
32069            $('div.orientation', $table.parents('div.data')).append($pagination);
32070          }
32071        } else {
32072          $('div.orientation', $table.parents('div.data')).show();
32073          $pagecount = Math.ceil($outline.count / $table.data('limit'))
32074  //        $total = $('<div class="total">'+$table.data('start')+' to '+((($table.data('start')+$table.data('limit')-1)<$outline.count) ? ($table.data('start')+$table.data('limit')-1) : $outline.count)+' of '+$outline.count+' items</div>');
32075          $pagination = $('<div class="pagination"></div>');
32076          $pagination.append('<span class="spacer">'+$table.data('start')+' to '+((($table.data('start')+$table.data('limit')-1)<$outline.count) ? ($table.data('start')+$table.data('limit')-1) : $outline.count)+' of '+$outline.count+'</span>');
32077  //        for ($i = 1; $i <= $pagecount; $i++) {
32078  //          if (($table.data('start') > (($i-1)*$table.data('limit'))) && ($table.data('start') < ($i*$table.data('limit')))) {
32079  //            $pagination.append('<span class="spacer">'+$i+'</span>');
32080  //          } else {
32081  //            $link = $('<a href="#" data-start="'+((($i-1)*$table.data('limit'))+1)+'">'+$i+'</a>')
32082  //            $link.click(function(e){
32083  //              $table = $('table.ajax', $(this).parents('div.data'))
32084  //              $table.data('start', $(this).data('start'));
32085  //              refresh_table_rows($table);
32086  //              e.preventDefault();
32087  //            });
32088  //            $pagination.append($link);
32089  //          }
32090  //        }
32091          if ($table.data('start') == 1) {
32092            $pagination.append('<span class="mdi mdi-chevron-left disabled button"></span>');
32093          } else {
32094            $link = $('<a href="#" class="mdi mdi-chevron-left secondary button" data-start="'+($table.data('start')-$table.data('limit'))+'"></a>')
32095            $link.click(function(e){
32096              $table = $('table.ajax', $(this).parents('div.data'))
32097              $table.data('start', $(this).data('start'));
32098              refresh_table_rows($table);
32099              e.preventDefault();
32100            });
32101            $pagination.append($link);
32102          }
32103          if ($table.data('start') < (($pagecount-1)*$table.data('limit'))) {
32104            $link = $('<a href="#" class="mdi mdi-chevron-right secondary button" data-start="'+($table.data('start')+$table.data('limit'))+'"></a>')
32105            $link.click(function(e){
32106              $table = $('table.ajax', $(this).parents('div.data'))
32107              $table.data('start', $(this).data('start'));
32108              refresh_table_rows($table);
32109              e.preventDefault();
32110            });
32111            $pagination.append($link);
32112          } else {
32113            $pagination.append('<span class="mdi mdi-chevron-right disabled button"></span>');
32114          }
32115  //        $('div.orientation', $table.parents('div.data')).prepend($total);
32116          $('div.orientation', $table.parents('div.data')).append($pagination);
32117          if ($('tbody tr', $table).length < 1) {
32118            $('tbody', $table).append('<tr><td colspan="'+$('th', $table).length+'" class="textcenter">Loading...</td></tr>');
32119          }
32120        }
32121      }
32122
32123      $('div.summary', $table.parents('div.data')).html('')
32124      $.each($outline.summary, function($key, $value){
32125        $('div.summary', $table.parents('div.data')).append('<div class="panel"><div class="label small grey">'+$value['label']+'</div><div class="field xxlarge">'+$value['field']+'</div></div>');
32126      });
32127
32128      $records_request = $.ajax({
32129        dataType: 'json',
32130        data: {
32131          scope: $table.data('scope'),
32132          filter: $table.data('filter'),
32133          search: $table.data('search'),
32134          sort: $table.data('sort'),
32135          start: $table.data('start'),
32136          limit: $table.data('limit')
32137        },
32138        cache: false,
32139        url: $table.data('source'),
32140        success: $.proxy(function($records) {
32141
32142          $('tbody', $table).empty();
32143          $('tbody', $table).removeClass('disable');
32144          if ($records.length > 0) {
32145            $.each($records, $.proxy(function($key, $value) {
32146              if ($value['table_row']) {
32147                $('tbody', $table).append($value['table_row'])
32148              } else {
32149                console.log('table_row not found in index.json');
32150              }
32151            }, this));
32152            start_table_rowclick($table);
32153            start_table_data_operations($table);
32154            start_table_actionsmenu($table);
32155          } else {
32156            $('tbody', $table).append('<tr><td colspan="'+$('th', $table).length+'" class="textcenter">No items</td></tr>');
32157          }
32158
32159        }, this)
32160      });
32161
32162    }, this)
32163  });
32164
32165}
32166
32167function start_table_order() {
32168
32169//  $('div.overflow').nestable({
32170//    listNodeName    : 'table',
32171//    itemNodeName    : 'tr.question',
32172//    rootClass       : 'overflow',
32173//    listClass       : 'dd-list',
32174//    itemClass       : 'dd-item',
32175//    dragClass       : 'dd-dragel',
32176//    handleClass     : 'dd-handle',
32177//    collapsedClass  : 'dd-collapsed',
32178//    placeClass      : 'dd-placeholder',
32179//    noDragClass     : 'dd-nodrag',
32180//    emptyClass      : 'dd-empty',
32181//    expandBtnHTML   : '<button data-action="expand" type="button">Expand</button>',
32182//    collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
32183//    group           : 0,
32184//    maxDepth        : 1,
32185//    threshold       : 20
32186//  });
32187
32188}
32189
32190function start_table_rowclick($table) {
32191
32192  $('tr.rowclick td:not(.select):not(.actions)', $table).click(function(){
32193    $('a.rowclick', $(this).parents('tr.rowclick'))[0].click();
32194  });
32195
32196}
32197
32198function start_table_head_operations($table) {
32199
32200  if ($('th.select', $table).length > 0) {
32201
32202    $('div.operations', $table.parents('div.data')).hide();
32203
32204    $('th.select input[type=checkbox]', $table).change(function(){
32205      $result_ids = $table.data('result_ids');
32206      if ($(this).is(':checked')) {
32207        $table.data('select_ids', $result_ids);
32208        $('td.select input[type=checkbox]').prop('checked', true);
32209        $label = $result_ids.length+' selected';
32210        $('div.operations > ul > li > a', $table.parents('div.data')).text($label);
32211        $('div.operations', $table.parents('div.data')).show();
32212      } else {
32213        $table.data('select_ids', []);
32214        $('td.select input[type=checkbox]').prop('checked', false);
32215        $('div.operations', $table.parents('div.data')).hide();
32216      }
32217    });
32218
32219  }
32220
32221}
32222
32223function start_table_data_operations($table) {
32224
32225  if ($('th.select', $table).length > 0) {
32226
32227    $select_ids = $table.data('select_ids');
32228    $('td.select input[type=checkbox]', $table).each(function(){
32229      $item_id = parseInt($(this).attr('name').replace('select-',''));
32230      if ($select_ids.includes($item_id)) {
32231        $(this).prop('checked', true);
32232      }
32233    });
32234
32235    $('td.select input[type=checkbox]', $table).change(function(){
32236      $('th.select input[type=checkbox]').prop('checked', false);
32237      $select_ids = [];
32238      $('td.select input[type=checkbox]').each(function(){
32239        if ($(this).is(':checked')) {
32240          $item_id = parseInt($(this).attr('name').replace('select-',''));
32241          $select_ids.push($item_id);
32242        }
32243      });
32244      if ($select_ids.length > 0) {
32245        $label = $select_ids.length+' selected';
32246        $('div.operations > ul > li > a', $table.parents('div.data')).text($label);
32247        $('div.operations', $table.parents('div.data')).show();
32248      } else {
32249        $('div.operations', $table.parents('div.data')).hide();
32250      }
32251      $table.data('select_ids', $select_ids);
32252    });
32253
32254  }
32255
32256}
32257
32258function start_table_actionsmenu($table) {
32259
32260  $('td.actions.menu', $table).each(function(){
32261    $('ul', $(this)).hide();
32262    $('a.toggle', $(this)).click(function(e){
32263      $('ul', $('td.actions.menu', $table).not($(this).parents('td.menu'))).hide();
32264      $('ul', $(this).parents('td.menu')).toggle();
32265      e.preventDefault();
32266    });
32267  });
32268
32269  $(document).click (function (e) {
32270    if ($(e.target).parents('td.menu').length < 1) {
32271      $('td.menu ul').hide();
32272    }
32273  });
32274
32275};
32276/*! algoliasearch 3.20.3 | © 2014, 2015 Algolia SAS | github.com/algolia/algoliasearch-client-js */
32277!function(e){var t;"undefined"!=typeof window?t=window:"undefined"!=typeof self&&(t=self),t.ALGOLIA_MIGRATION_LAYER=e()}(function(){return function e(t,r,n){function o(s,a){if(!r[s]){if(!t[s]){var c="function"==typeof require&&require;if(!a&&c)return c(s,!0);if(i)return i(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var l=r[s]={exports:{}};t[s][0].call(l.exports,function(e){var r=t[s][1][e];return o(r?r:e)},l,l.exports,e,t,r,n)}return r[s].exports}for(var i="function"==typeof require&&require,s=0;s<n.length;s++)o(n[s]);return o}({1:[function(e,t,r){function n(e,t){for(var r in t)e.setAttribute(r,t[r])}function o(e,t){e.onload=function(){this.onerror=this.onload=null,t(null,e)},e.onerror=function(){this.onerror=this.onload=null,t(new Error("Failed to load "+this.src),e)}}function i(e,t){e.onreadystatechange=function(){"complete"!=this.readyState&&"loaded"!=this.readyState||(this.onreadystatechange=null,t(null,e))}}t.exports=function(e,t,r){var s=document.head||document.getElementsByTagName("head")[0],a=document.createElement("script");"function"==typeof t&&(r=t,t={}),t=t||{},r=r||function(){},a.type=t.type||"text/javascript",a.charset=t.charset||"utf8",a.async=!("async"in t)||!!t.async,a.src=e,t.attrs&&n(a,t.attrs),t.text&&(a.text=""+t.text);var c="onload"in a?o:i;c(a,r),a.onload||o(a,r),s.appendChild(a)}},{}],2:[function(e,t,r){"use strict";function n(e){for(var t=new RegExp("cdn\\.jsdelivr\\.net/algoliasearch/latest/"+e.replace(".","\\.")+"(?:\\.min)?\\.js$"),r=document.getElementsByTagName("script"),n=!1,o=0,i=r.length;o<i;o++)if(r[o].src&&t.test(r[o].src)){n=!0;break}return n}t.exports=n},{}],3:[function(e,t,r){"use strict";function n(t){var r=e(1),n="//cdn.jsdelivr.net/algoliasearch/2/"+t+".min.js",i="-- AlgoliaSearch `latest` warning --\nWarning, you are using the `latest` version string from jsDelivr to load the AlgoliaSearch library.\
32277nUsing `latest` is no more recommended, you should load //cdn.jsdelivr.net/algoliasearch/2/algoliasearch.min.js\n\nAlso, we updated the AlgoliaSearch JavaScript client to V3. If you want to upgrade,\nplease read our migration guide at https://github.com/algolia/algoliasearch-client-js/wiki/Migration-guide-from-2.x.x-to-3.x.x\n-- /AlgoliaSearch  `latest` warning --";window.console&&(window.console.warn?window.console.warn(i):window.console.log&&window.console.log(i));try{document.write("<script>window.ALGOLIA_SUPPORTS_DOCWRITE = true</script>"),window.ALGOLIA_SUPPORTS_DOCWRITE===!0?(document.write('<script src="'+n+'"></script>'),o("document.write")()):r(n,o("DOMElement"))}catch(s){r(n,o("DOMElement"))}}function o(e){return function(){var t="AlgoliaSearch: loaded V2 script using "+e;window.console&&window.console.log&&window.console.log(t)}}t.exports=n},{1:1}],4:[function(e,t,r){"use strict";function n(){var e="-- AlgoliaSearch V2 => V3 error --\nYou are trying to use a new version of the AlgoliaSearch JavaScript client with an old notation.\nPlease read our migration guide at https://github.com/algolia/algoliasearch-client-js/wiki/Migration-guide-from-2.x.x-to-3.x.x\n-- /AlgoliaSearch V2 => V3 error --";window.AlgoliaSearch=function(){throw new Error(e)},window.AlgoliaSearchHelper=function(){throw new Error(e)},window.AlgoliaExplainResults=function(){throw new Error(e)}}t.exports=n},{}],5:[function(e,t,r){"use strict";function n(t){var r=e(2),n=e(3),o=e(4);r(t)?n(t):o()}n("algoliasearch")},{2:2,3:3,4:4}]},{},[5])(5)}),function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.algoliasearch=e()}}(function(){var e;return function t(e,r,n){function o(s,a){if(!r[s]){if(!e[s]){var c="function"==typeof require&&require;if(!a&&c)return c(s,!0);if(i)return i(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var l=r[s]={exports:{}};e[s][0].call(l.exports,function(t){var r=e[s][1][t];return o(r?r:t)},l,l.exports,t,e,r,n)}return r[s].exports}for(var i="function"==typeof require&&require,s=0;s<n.length;s++)o(n[s]);return o}({1:[function(e,t,r){(function(n){function o(){return"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}function i(){var e=arguments,t=this.useColors;if(e[0]=(t?"%c":"")+this.namespace+(t?" %c":" ")+e[0]+(t?"%c ":" ")+"+"+r.humanize(this.diff),!t)return e;var n="color: "+this.color;e=[e[0],n,"color: inherit"].concat(Array.prototype.slice.call(e,1));var o=0,i=0;return e[0].replace(/%[a-z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,n),e}function s(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(e){try{null==e?r.storage.removeItem("debug"):r.storage.debug=e}catch(t){}}function c(){try{return r.storage.debug}catch(e){}if("undefined"!=typeof n&&"env"in n)return n.env.DEBUG}function u(){try{return window.localStorage}catch(e){}}r=t.exports=e(2),r.log=s,r.formatArgs=i,r.save=a,r.load=c,r.useColors=o,r.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),r.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],r.formatters.j=function(e){try{return JSON.stringify(e)}catch(t){return"[UnexpectedJSONParseError]: "+t.message}},r.enable(c())}).call(this,e(12))},{12:12,2:2}],2:[function(e,t,r){function n(){return r.colors[l++%r.colors.length]}function o(e){function t(){}function o(){var e=o,t=+new Date,i=t-(u||t);e.diff=i,e.prev=u,e.curr=t,u=t,null==e.useColors&&(e.useColors=r.useColors()),null==e.color&&e.useColors&&(e.color=n());for(var s=new Array(arguments.length),a=0;a<s.length;a++)s[a]=arguments[a];s[0]=r.coerce(s[0]),"string"!=typeof s[0]&&(s=["%o"].concat(s));var c=0;s[0]=s[0].replace(/%([a-z%])/g,function(t,n){if("%%"===t)return t;c++;var o=r.formatters[n];if("function"==typeof o){var i=s[c];
32277t=o.call(e,i),s.splice(c,1),c--}return t}),s=r.formatArgs.apply(e,s);var l=o.log||r.log||console.log.bind(console);l.apply(e,s)}t.enabled=!1,o.enabled=!0;var i=r.enabled(e)?o:t;return i.namespace=e,i}function i(e){r.save(e);for(var t=(e||"").split(/[\s,]+/),n=t.length,o=0;o<n;o++)t[o]&&(e=t[o].replace(/[\\^$+?.()|[\]{}]/g,"\\$&").replace(/\*/g,".*?"),"-"===e[0]?r.skips.push(new RegExp("^"+e.substr(1)+"$")):r.names.push(new RegExp("^"+e+"$")))}function s(){r.enable("")}function a(e){var t,n;for(t=0,n=r.skips.length;t<n;t++)if(r.skips[t].test(e))return!1;for(t=0,n=r.names.length;t<n;t++)if(r.names[t].test(e))return!0;return!1}function c(e){return e instanceof Error?e.stack||e.message:e}r=t.exports=o.debug=o,r.coerce=c,r.disable=s,r.enable=i,r.enabled=a,r.humanize=e(9),r.names=[],r.skips=[],r.formatters={};var u,l=0},{9:9}],3:[function(t,r,n){(function(o,i){!function(t,o){"object"==typeof n&&"undefined"!=typeof r?r.exports=o():"function"==typeof e&&e.amd?e(o):t.ES6Promise=o()}(this,function(){"use strict";function e(e){return"function"==typeof e||"object"==typeof e&&null!==e}function r(e){return"function"==typeof e}function n(e){X=e}function s(e){W=e}function a(){return function(){return o.nextTick(h)}}function c(){return"undefined"!=typeof V?function(){V(h)}:p()}function u(){var e=0,t=new Z(h),r=document.createTextNode("");return t.observe(r,{characterData:!0}),function(){r.data=e=++e%2}}function l(){var e=new MessageChannel;return e.port1.onmessage=h,function(){return e.port2.postMessage(0)}}function p(){var e=setTimeout;return function(){return e(h,1)}}function h(){for(var e=0;e<G;e+=2){var t=re[e],r=re[e+1];t(r),re[e]=void 0,re[e+1]=void 0}G=0}function d(){try{var e=t,r=e("vertx");return V=r.runOnLoop||r.runOnContext,c()}catch(n){return p()}}function f(e,t){var r=arguments,n=this,o=new this.constructor(m);void 0===o[oe]&&C(o);var i=n._state;return i?!function(){var e=r[i-1];W(function(){return P(i,o,e,n._result)})}():O(n,o,e,t),o}function y(e){var t=this;if(e&&"object"==typeof e&&e.constructor===t)return e;var r=new t(m);return j(r,e),r}function m(){}function v(){return new TypeError("You cannot resolve a promise with itself")}function g(){return new TypeError("A promises callback cannot return that same promise.")}function b(e){try{return e.then}catch(t){return ce.error=t,ce}}function w(e,t,r,n){try{e.call(t,r,n)}catch(o){return o}}function _(e,t,r){W(function(e){var n=!1,o=w(r,t,function(r){n||(n=!0,t!==r?j(e,r):I(e,r))},function(t){n||(n=!0,k(e,t))},"Settle: "+(e._label||" unknown promise"));!n&&o&&(n=!0,k(e,o))},e)}function x(e,t){t._state===se?I(e,t._result):t._state===ae?k(e,t._result):O(t,void 0,function(t){return j(e,t)},function(t){return k(e,t)})}function T(e,t,n){t.constructor===e.constructor&&n===f&&t.constructor.resolve===y?x(e,t):n===ce?k(e,ce.error):void 0===n?I(e,t):r(n)?_(e,t,n):I(e,t)}function j(t,r){t===r?k(t,v()):e(r)?T(t,r,b(r)):I(t,r)}function S(e){e._onerror&&e._onerror(e._result),R(e)}function I(e,t){e._state===ie&&(e._result=t,e._state=se,0!==e._subscribers.length&&W(R,e))}function k(e,t){e._state===ie&&(e._state=ae,e._result=t,W(S,e))}function O(e,t,r,n){var o=e._subscribers,i=o.length;e._onerror=null,o[i]=t,o[i+se]=r,o[i+ae]=n,0===i&&e._state&&W(R,e)}function R(e){var t=e._subscribers,r=e._state;if(0!==t.length){for(var n=void 0,o=void 0,i=e._result,s=0;s<t.length;s+=3)n=t[s],o=t[s+r],n?P(r,n,o,i):o(i);e._subscribers.length=0}}function A(){this.error=null}function E(e,t){try{return e(t)}catch(r){return ue.error=r,ue}}function P(e,t,n,o){var i=r(n),s=void 0,a=void 0,c=void 0,u=void 0;if(i){if(s=E(n,o),s===ue?(u=!0,a=s.error,s=null):c=!0,t===s)return void k(t,g())}else s=o,c=!0;t._state!==ie||(i&&c?j(t,s):u?k(t,a):e===se?I(t,s):e===ae&&k(t,s))}function U(e,t){try{t(function(t){j(e,t)},function(t){k(e,t)})}catch(r){k(e,r)}}function q(){return le++}function C(e){e[oe]=le++,e._state=void 0,e._result=void 0,e._subscribers=[]}function N(e,t){this._instanceConstructor=e,this.promise=new e(m),this.promise[oe]||C(this.promise),Q(t)?(this._input=t,this.length=t.length,this._remaining=t.length,this._result=new Array(this.length),0===this.length?I(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&I(this.promise,this._result))):k(this.promise,D())}function D(){return new Error("Array Methods must be provided an Array")}function L(e){return new N(this,e).promise}function H(e){var t=this;return new t(Q(e)?function(r,n){for(var o=e.length,i=0;i<o;i++)t.resolve(e[i]).then(r,n)}:function(e,t){return t(new TypeError("You must pass an array to race."))})}function M(e){var t=this,r=new t(m);return k(r,e),r}function J(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function F(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object c
32277onstructor cannot be called as a function.")}function K(e){this[oe]=q(),this._result=this._state=void 0,this._subscribers=[],m!==e&&("function"!=typeof e&&J(),this instanceof K?U(this,e):F())}function B(){var e=void 0;if("undefined"!=typeof i)e=i;else if("undefined"!=typeof self)e=self;else try{e=Function("return this")()}catch(t){throw new Error("polyfill failed because global object is unavailable in this environment")}var r=e.Promise;if(r){var n=null;try{n=Object.prototype.toString.call(r.resolve())}catch(t){}if("[object Promise]"===n&&!r.cast)return}e.Promise=K}var $=void 0;$=Array.isArray?Array.isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)};var Q=$,G=0,V=void 0,X=void 0,W=function(e,t){re[G]=e,re[G+1]=t,G+=2,2===G&&(X?X(h):ne())},Y="undefined"!=typeof window?window:void 0,z=Y||{},Z=z.MutationObserver||z.WebKitMutationObserver,ee="undefined"==typeof self&&"undefined"!=typeof o&&"[object process]"==={}.toString.call(o),te="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,re=new Array(1e3),ne=void 0;ne=ee?a():Z?u():te?l():void 0===Y&&"function"==typeof t?d():p();var oe=Math.random().toString(36).substring(16),ie=void 0,se=1,ae=2,ce=new A,ue=new A,le=0;return N.prototype._enumerate=function(){for(var e=this.length,t=this._input,r=0;this._state===ie&&r<e;r++)this._eachEntry(t[r],r)},N.prototype._eachEntry=function(e,t){var r=this._instanceConstructor,n=r.resolve;if(n===y){var o=b(e);if(o===f&&e._state!==ie)this._settledAt(e._state,t,e._result);else if("function"!=typeof o)this._remaining--,this._result[t]=e;else if(r===K){var i=new r(m);T(i,e,o),this._willSettleAt(i,t)}else this._willSettleAt(new r(function(t){return t(e)}),t)}else this._willSettleAt(n(e),t)},N.prototype._settledAt=function(e,t,r){var n=this.promise;n._state===ie&&(this._remaining--,e===ae?k(n,r):this._result[t]=r),0===this._remaining&&I(n,this._result)},N.prototype._willSettleAt=function(e,t){var r=this;O(e,void 0,function(e){return r._settledAt(se,t,e)},function(e){return r._settledAt(ae,t,e)})},K.all=L,K.race=H,K.resolve=y,K.reject=M,K._setScheduler=n,K._setAsap=s,K._asap=W,K.prototype={constructor:K,then:f,"catch":function(e){return this.then(null,e)}},K.polyfill=B,K.Promise=K,K})}).call(this,t(12),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{12:12}],4:[function(e,t,r){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function o(e){return"function"==typeof e}function i(e){return"number"==typeof e}function s(e){return"object"==typeof e&&null!==e}function a(e){return void 0===e}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(e){if(!i(e)||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,r,n,i,c,u;if(this._events||(this._events={}),"error"===e&&(!this._events.error||s(this._events.error)&&!this._events.error.length)){if(t=arguments[1],t instanceof Error)throw t;var l=new Error('Uncaught, unspecified "error" event. ('+t+")");throw l.context=t,l}if(r=this._events[e],a(r))return!1;if(o(r))switch(arguments.length){case 1:r.call(this);break;case 2:r.call(this,arguments[1]);break;case 3:r.call(this,arguments[1],arguments[2]);break;default:i=Array.prototype.slice.call(arguments,1),r.apply(this,i)}else if(s(r))for(i=Array.prototype.slice.call(arguments,1),u=r.slice(),n=u.length,c=0;c<n;c++)u[c].apply(this,i);return!0},n.prototype.addListener=function(e,t){var r;if(!o(t))throw TypeError("listener must be a function");return this._events||(this._events={}),this._events.newListener&&this.emit("newListener",e,o(t.listener)?t.listener:t),this._events[e]?s(this._events[e])?this._events[e].push(t):this._events[e]=[this._events[e],t]:this._events[e]=t,s(this._events[e])&&!this._events[e].warned&&(r=a(this._maxListeners)?n.defaultMaxListeners:this._maxListeners,r&&r>0&&this._events[e].length>r&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace())),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function r(){this.removeListener(e,r),n||(n=!0,t.apply(this,arguments))}if(!o(t))throw TypeError("listener must be a function");var n=!1;return r.listener=t,this.on(e,r),this},n.prototype.removeListener=function(e,t){var r,n,i,a;if(!o(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(r=this._events[e],i=r.length,n=-1,r===t||o(r.listener)&&r.listener===t)delete this._events[e],this._events.removeListener&&this.emit
32277("removeListener",e,t);else if(s(r)){for(a=i;a-- >0;)if(r[a]===t||r[a].listener&&r[a].listener===t){n=a;break}if(n<0)return this;1===r.length?(r.length=0,delete this._events[e]):r.splice(n,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,r;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(r=this._events[e],o(r))this.removeListener(e,r);else if(r)for(;r.length;)this.removeListener(e,r[r.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?o(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(o(t))return 1;if(t)return t.length}return 0},n.listenerCount=function(e,t){return e.listenerCount(t)}},{}],5:[function(e,t,r){var n=Object.prototype.hasOwnProperty,o=Object.prototype.toString;t.exports=function(e,t,r){if("[object Function]"!==o.call(t))throw new TypeError("iterator must be a function");var i=e.length;if(i===+i)for(var s=0;s<i;s++)t.call(r,e[s],s,e);else for(var a in e)n.call(e,a)&&t.call(r,e[a],a,e)}},{}],6:[function(e,t,r){(function(e){"undefined"!=typeof window?t.exports=window:"undefined"!=typeof e?t.exports=e:"undefined"!=typeof self?t.exports=self:t.exports={}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],7:[function(e,t,r){"function"==typeof Object.create?t.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(e,t){e.super_=t;var r=function(){};r.prototype=t.prototype,e.prototype=new r,e.prototype.constructor=e}},{}],8:[function(e,t,r){var n={}.toString;t.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},{}],9:[function(e,t,r){function n(e){if(e=String(e),!(e.length>1e4)){var t=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(t){var r=parseFloat(t[1]),n=(t[2]||"ms").toLowerCase();switch(n){case"years":case"year":case"yrs":case"yr":case"y":return r*p;case"days":case"day":case"d":return r*l;case"hours":case"hour":case"hrs":case"hr":case"h":return r*u;case"minutes":case"minute":case"mins":case"min":case"m":return r*c;case"seconds":case"second":case"secs":case"sec":case"s":return r*a;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}}}function o(e){return e>=l?Math.round(e/l)+"d":e>=u?Math.round(e/u)+"h":e>=c?Math.round(e/c)+"m":e>=a?Math.round(e/a)+"s":e+"ms"}function i(e){return s(e,l,"day")||s(e,u,"hour")||s(e,c,"minute")||s(e,a,"second")||e+" ms"}function s(e,t,r){if(!(e<t))return e<1.5*t?Math.floor(e/t)+" "+r:Math.ceil(e/t)+" "+r+"s"}var a=1e3,c=60*a,u=60*c,l=24*u,p=365.25*l;t.exports=function(e,t){t=t||{};var r=typeof e;if("string"===r&&e.length>0)return n(e);if("number"===r&&isNaN(e)===!1)return t["long"]?i(e):o(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},{}],10:[function(e,t,r){"use strict";var n=Object.prototype.hasOwnProperty,o=Object.prototype.toString,i=Array.prototype.slice,s=e(11),a=Object.prototype.propertyIsEnumerable,c=!a.call({toString:null},"toString"),u=a.call(function(){},"prototype"),l=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],p=function(e){var t=e.constructor;return t&&t.prototype===e},h={$console:!0,$external:!0,$frame:!0,$frameElement:!0,$frames:!0,$innerHeight:!0,$innerWidth:!0,$outerHeight:!0,$outerWidth:!0,$pageXOffset:!0,$pageYOffset:!0,$parent:!0,$scrollLeft:!0,$scrollTop:!0,$scrollX:!0,$scrollY:!0,$self:!0,$webkitIndexedDB:!0,$webkitStorageInfo:!0,$window:!0},d=function(){if("undefined"==typeof window)return!1;for(var e in window)try{if(!h["$"+e]&&n.call(window,e)&&null!==window[e]&&"object"==typeof window[e])try{p(window[e])}catch(t){return!0}}catch(t){return!0}return!1}(),f=function(e){if("undefined"==typeof window||!d)return p(e);try{return p(e)}catch(t){return!1}},y=function(e){var t=null!==e&&"object"==typeof e,r="[object Function]"===o.call(e),i=s(e),a=t&&"[object String]"===o.call(e),p=[];if(!t&&!r&&!i)throw new TypeError("Object.keys called on a non-object");var h=u&&r;if(a&&e.length>0&&!n.call(e,0))for(var d=0;d<e.length;++d)p.push(String(d));if(i&&e.length>0)for(var y=0;y<e.length;++y)p.push(String(y));else for(var m in e)h&&"prototype"===m||!n.call(e,m)||p.push(String(m));if(c)for(var v=f(e),g=0;g<l.length;++g)v&&"constructor"===l[g]||!n.call(e,l[g])||p.push(l[g]);return p};y.shim=function(){if(Object.keys){var e=function(){return 2===(Object.keys(arguments)||"").length}(1,2);if(!e){var t=Object.keys;
vendor: 7,601 bytes, line 32277
32277Object.keys=function(e){return t(s(e)?i.call(e):e)}}}else Object.keys=y;return Object.keys||y},t.exports=y},{11:11}],11:[function(e,t,r){"use strict";var n=Object.prototype.toString;t.exports=function(e){var t=n.call(e),r="[object Arguments]"===t;return r||(r="[object Array]"!==t&&null!==e&&"object"==typeof e&&"number"==typeof e.length&&e.length>=0&&"[object Function]"===n.call(e.callee)),r}},{}],12:[function(e,t,r){function n(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function i(e){if(p===setTimeout)return setTimeout(e,0);if((p===n||!p)&&setTimeout)return p=setTimeout,setTimeout(e,0);try{return p(e,0)}catch(t){try{return p.call(null,e,0)}catch(t){return p.call(this,e,0)}}}function s(e){if(h===clearTimeout)return clearTimeout(e);if((h===o||!h)&&clearTimeout)return h=clearTimeout,clearTimeout(e);try{return h(e)}catch(t){try{return h.call(null,e)}catch(t){return h.call(this,e)}}}function a(){m&&f&&(m=!1,f.length?y=f.concat(y):v=-1,y.length&&c())}function c(){if(!m){var e=i(a);m=!0;for(var t=y.length;t;){for(f=y,y=[];++v<t;)f&&f[v].run();v=-1,t=y.length}f=null,m=!1,s(e)}}function u(e,t){this.fun=e,this.array=t}function l(){}var p,h,d=t.exports={};!function(){try{p="function"==typeof setTimeout?setTimeout:n}catch(e){p=n}try{h="function"==typeof clearTimeout?clearTimeout:o}catch(e){h=o}}();var f,y=[],m=!1,v=-1;d.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var r=1;r<arguments.length;r++)t[r-1]=arguments[r];y.push(new u(e,t)),1!==y.length||m||i(c)},u.prototype.run=function(){this.fun.apply(null,this.array)},d.title="browser",d.browser=!0,d.env={},d.argv=[],d.version="",d.versions={},d.on=l,d.addListener=l,d.once=l,d.off=l,d.removeListener=l,d.removeAllListeners=l,d.emit=l,d.binding=function(e){throw new Error("process.binding is not supported")},d.cwd=function(){return"/"},d.chdir=function(e){throw new Error("process.chdir is not supported")},d.umask=function(){return 0}},{}],13:[function(e,t,r){"use strict";function n(e,t){if(e.map)return e.map(t);for(var r=[],n=0;n<e.length;n++)r.push(t(e[n],n));return r}var o=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};t.exports=function(e,t,r,a){return t=t||"&",r=r||"=",null===e&&(e=void 0),"object"==typeof e?n(s(e),function(s){var a=encodeURIComponent(o(s))+r;return i(e[s])?n(e[s],function(e){return a+encodeURIComponent(o(e))}).join(t):a+encodeURIComponent(o(e[s]))}).join(t):a?encodeURIComponent(o(a))+r+encodeURIComponent(o(e)):""};var i=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},s=Object.keys||function(e){var t=[];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.push(r);return t}},{}],14:[function(e,t,r){function n(){c.apply(this,arguments)}function o(){var e="Not implemented in this environment.\nIf you feel this is a mistake, write to [email protected]";throw new l.AlgoliaSearchError(e)}t.exports=n;var i=e(16),s=e(26),a=e(27),c=e(15),u=e(7),l=e(28);u(n,c),n.prototype.deleteIndex=function(e,t){return this._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(e),hostType:"write",callback:t})},n.prototype.moveIndex=function(e,t,r){var n={operation:"move",destination:t};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(e)+"/operation",body:n,hostType:"write",callback:r})},n.prototype.copyIndex=function(e,t,r){var n={operation:"copy",destination:t};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(e)+"/operation",body:n,hostType:"write",callback:r})},n.prototype.getLogs=function(t,r,n){var o=e(25),i={};return"object"==typeof t?(i=o(t),n=r):0===arguments.length||"function"==typeof t?n=t:1===arguments.length||"function"==typeof r?(n=r,i.offset=t):(i.offset=t,i.length=r),void 0===i.offset&&(i.offset=0),void 0===i.length&&(i.length=10),this._jsonRequest({method:"GET",url:"/1/logs?"+this._getSearchParams(i,""),hostType:"read",callback:n})},n.prototype.listIndexes=function(e,t){var r="";return void 0===e||"function"==typeof e?t=e:r="?page="+e,this._jsonRequest({method:"GET",url:"/1/indexes"+r,hostType:"read",callback:t})},n.prototype.initIndex=function(e){return new i(this,e)},n.prototype.listUserKeys=function(e){return this._jsonRequest({method:"GET",url:"/1/keys",hostType:"read",callback:e})},n.prototype.getUserKeyACL=function(e,t){return this._jsonRequest({method:"GET",url:"/1/keys/"+e,hostType:"read",callback:t})},n.prototype.deleteUserKey=function(e,t){return this._jsonRequest({method:"DELETE",url:"/1/keys/"+e,hostType:"write",callback:t})},n.prototype.addUserKey=function(t,r,n){var o=e(8),i="Usage: client.addUserKey(arrayOfAcls[, params, callback])";if(!o(t))throw new Error(i);1!==arguments.length&&"function"!=typeof r||(n=r,r=null);var s={acl:t};return r&&(s.validity=r.validity,s.maxQueriesPerIPPerHour=r.maxQueriesPerIPPerHour,s.maxHitsPerQuery=r.maxHitsPerQuery,s.indexes=r.indexes,s.description=r.description,r.queryParameters&&(s.queryParameters=this._getSearchParams(r.queryParameters,"")),s.referers=r.referers),this._jsonRequest({method:"POST",url:"/1/keys",body:s,hostType:"write",callback:n})},n.prototype.addUserKeyWithValidity=s(function(e,t,r){return this.addUserKey(e,t,r)},a("client.addUserKeyWithValidity()","client.addUserKey()")),n.prototype.updateUserKey=function(t,r,n,o){var i=e(8),s="Usage: client.updateUserKey(key, arrayOfAcls[, params, callback])";if(!i(r))throw new Error(s);2!==arguments.length&&"function"!=typeof n||(o=n,n=null);var a={acl:r};return n&&(a.validity=n.validity,a.maxQueriesPerIPPerHour=n.maxQueriesPerIPPerHour,a.maxHitsPerQuery=n.maxHitsPerQuery,a.indexes=n.indexes,a.description=n.description,n.queryParameters&&(a.queryParameters=this._getSearchParams(n.queryParameters,"")),a.referers=n.referers),this._jsonRequest({method:"PUT",url:"/1/keys/"+t,body:a,hostType:"write",callback:o})},n.prototype.startQueriesBatch=s(function(){this._batch=[]},a("client.startQueriesBatch()","client.search()")),n.prototype.addQueryInBatch=s(function(e,t,r){this._batch.push({indexName:e,query:t,params:r})},a("client.addQueryInBatch()","client.search()")),n.prototype.sendQueriesBatch=s(function(e){return this.search(this._batch,e)},a("client.sendQueriesBatch()","client.search()")),n.prototype.batch=function(t,r){var n=e(8),o="Usage: client.batch(operations[, callback])";if(!n(t))throw new Error(o);return this._jsonRequest({method:"POST",url:"/1/indexes/*/batch",body:{requests:t},hostType:"write",callback:r})},n.prototype.destroy=o,n.prototype.enableRateLimitForward=o,n.prototype.disableRateLimitForward=o,n.prototype.useSecuredAPIKey=o,n.prototype.disableSecuredAPIKey=o,n.prototype.generateSecuredApiKey=o},{15:15,16:16,25:25,26:26,27:27,28:28,7:7,8:8}],15:[function(e,t,r){(function(r){function n(t,r,n){var i=e(1)("algoliasearch"),s=e(25),a=e(8),u=e(30),l="Usage: algoliasearch(applicationID, apiKey, opts)";if(n._allowEmptyCredentials!==!0&&!t)throw new c.AlgoliaSearchError("Please provide an application ID. "+l);if(n._allowEmptyCredentials!==!0&&!r)throw new c.AlgoliaSearchError("Please provide an API key. "+l);this.applicationID=t,this.apiKey=r,this.hosts={read:[],write:[]},n=n||{};var p=n.protocol||"https:";if(this._timeouts=n.timeouts||{connect:1e3,read:2e3,write:3e4},n.timeout&&(this._timeouts.connect=this._timeouts.read=this._timeouts.write=n.timeout),/:$/.test(p)||(p+=":"),"http:"!==n.protocol&&"https:"!==n.protocol)throw new c.AlgoliaSearchError("protocol must be `http:` or `https:` 
32277(was `"+n.protocol+"`)");if(this._checkAppIdData(),n.hosts)a(n.hosts)?(this.hosts.read=s(n.hosts),this.hosts.write=s(n.hosts)):(this.hosts.read=s(n.hosts.read),this.hosts.write=s(n.hosts.write));else{var h=u(this._shuffleResult,function(e){return t+"-"+e+".algolianet.com"});this.hosts.read=[this.applicationID+"-dsn.algolia.net"].concat(h),this.hosts.write=[this.applicationID+".algolia.net"].concat(h)}this.hosts.read=u(this.hosts.read,o(p)),this.hosts.write=u(this.hosts.write,o(p)),this.extraHeaders=[],this.cache=n._cache||{},this._ua=n._ua,this._useCache=!(void 0!==n._useCache&&!n._cache)||n._useCache,this._useFallback=void 0===n.useFallback||n.useFallback,this._setTimeout=n._setTimeout,i("init done, %j",this)}function o(e){return function(t){return e+"//"+t.toLowerCase()}}function i(e){if(void 0===Array.prototype.toJSON)return JSON.stringify(e);var t=Array.prototype.toJSON;delete Array.prototype.toJSON;var r=JSON.stringify(e);return Array.prototype.toJSON=t,r}function s(e){for(var t,r,n=e.length;0!==n;)r=Math.floor(Math.random()*n),n-=1,t=e[n],e[n]=e[r],e[r]=t;return e}function a(e){var t={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){var n;n="x-algolia-api-key"===r||"x-algolia-application-id"===r?"**hidden for security purposes**":e[r],t[r]=n}return t}t.exports=n;var c=e(28),u=e(29),l=e(18),p=e(34),h=500,d=r.env.RESET_APP_DATA_TIMER&&parseInt(r.env.RESET_APP_DATA_TIMER,10)||12e4;n.prototype.initIndex=function(e){return new l(this,e)},n.prototype.setExtraHeader=function(e,t){this.extraHeaders.push({name:e.toLowerCase(),value:t})},n.prototype.addAlgoliaAgent=function(e){this._ua.indexOf(";"+e)===-1&&(this._ua+=";"+e)},n.prototype._jsonRequest=function(t){function r(e,u){function h(e){var t=e&&e.body&&e.body.message&&e.body.status||e.statusCode||e&&e.body&&200;s("received response: statusCode: %s, computed statusCode: %d, headers: %j",e.statusCode,t,e.headers);var r=2===Math.floor(t/100),i=new Date;if(m.push({currentHost:x,headers:a(o),content:n||null,contentLength:void 0!==n?n.length:null,method:u.method,timeouts:u.timeouts,url:u.url,startTime:_,endTime:i,duration:i-_,statusCode:t}),r)return p._useCache&&l&&(l[w]=e.responseText),e.body;var h=4!==Math.floor(t/100);if(h)return d+=1,g();s("unrecoverable error");var f=new c.AlgoliaSearchError(e.body&&e.body.message,{debugData:m,statusCode:t});return p._promise.reject(f)}function v(e){s("error: %s, stack: %s",e.message,e.stack);var r=new Date;return m.push({currentHost:x,headers:a(o),content:n||null,contentLength:void 0!==n?n.length:null,method:u.method,timeouts:u.timeouts,url:u.url,startTime:_,endTime:r,duration:r-_}),e instanceof c.AlgoliaSearchError||(e=new c.Unknown(e&&e.message,e)),d+=1,e instanceof c.Unknown||e instanceof c.UnparsableJSON||d>=p.hosts[t.hostType].length&&(f||!y)?(e.debugData=m,p._promise.reject(e)):e instanceof c.RequestTimeout?b():g()}function g(){return s("retrying request"),p._incrementHostIndex(t.hostType),r(e,u)}function b(){return s("retrying request with higher timeout"),p._incrementHostIndex(t.hostType),p._incrementTimeoutMultipler(),u.timeouts=p._getTimeoutsForRequest(t.hostType),r(e,u)}p._checkAppIdData();var w,_=new Date;if(p._useCache&&(w=t.url),p._useCache&&n&&(w+="_body_"+u.body),p._useCache&&l&&void 0!==l[w])return s("serving response from cache"),p._promise.resolve(JSON.parse(l[w]));if(d>=p.hosts[t.hostType].length)return!y||f?(s("could not get any response"),
32278p._promise.reject(new c.AlgoliaSearchError("Cannot connect to the AlgoliaSearch API. Send an email to [email protected] to report and resolve the issue. Application id was: "+p.applicationID,{debugData:m}))):(s("switching to fallback"),d=0,u.method=t.fallback.method,u.url=t.fallback.url,u.jsonBody=t.fallback.body,u.jsonBody&&(u.body=i(u.jsonBody)),o=p._computeRequestHeaders(),u.timeouts=p._getTimeoutsForRequest(t.hostType),p._setHostIndexByType(0,t.hostType),f=!0,r(p._request.fallback,u));var x=p._getHostByType(t.hostType),T=x+u.url,j={body:u.body,jsonBody:u.jsonBody,method:u.method,headers:o,timeouts:u.timeouts,debug:s};return s("method: %s, url: %s, headers: %j, timeouts: %d",j.method,T,j.headers,j.timeouts),e===p._request.fallback&&s("using fallback"),e.call(p,T,j).then(h,v)}this._checkAppIdData();var n,o,s=e(1)("algoliasearch:"+t.url),l=t.cache,p=this,d=0,f=!1,y=p._useFallback&&p._request.fallback&&t.fallback;this.apiKey.length>h&&void 0!==t.body&&(void 0!==t.body.params||void 0!==t.body.requests)?(t.body.apiKey=this.apiKey,o=this._computeRequestHeaders(!1)):o=this._computeRequestHeaders(),void 0!==t.body&&(n=i(t.body)),s("request start");var m=[],v=r(p._request,{url:t.url,method:t.method,body:n,jsonBody:t.body,timeouts:p._getTimeoutsForRequest(t.hostType)});return t.callback?void v.then(function(e){u(function(){t.callback(null,e)},p._setTimeout||setTimeout)},function(e){u(function(){t.callback(e)},p._setTimeout||setTimeout)}):v},n.prototype._getSearchParams=function(e,t){if(void 0===e||null===e)return t;for(var r in e)null!==r&&void 0!==e[r]&&e.hasOwnProperty(r)&&(t+=""===t?"":"&",t+=r+"="+encodeURIComponent("[object Array]"===Object.prototype.toString.call(e[r])?i(e[r]):e[r]));return t},n.prototype._computeRequestHeaders=function(t){var r=e(5),n={"x-algolia-agent":this._ua,"x-algolia-application-id":this.applicationID};return t!==!1&&(n["x-algolia-api-key"]=this.apiKey),this.userToken&&(n["x-algolia-usertoken"]=this.userToken),this.securityTags&&(n["x-algolia-tagfilters"]=this.securityTags),this.extraHeaders&&r(this.extraHeaders,function(e){n[e.name]=e.value}),n},n.prototype.search=function(t,r,n){var o=e(8),i=e(30),s="Usage: client.search(arrayOfQueries[, callback])";if(!o(t))throw new Error(s);"function"==typeof r?(n=r,r={}):void 0===r&&(r={});var a=this,c={requests:i(t,function(e){var t="";return void 0!==e.query&&(t+="query="+encodeURIComponent(e.query)),{indexName:e.indexName,params:a._getSearchParams(e.params,t)}})},u=i(c.requests,function(e,t){return t+"="+encodeURIComponent("/1/indexes/"+encodeURIComponent(e.indexName)+"?"+e.params)}).join("&"),l="/1/indexes/*/queries";return void 0!==r.strategy&&(l+="?strategy="+r.strategy),this._jsonRequest({cache:this.cache,method:"POST",url:l,body:c,hostType:"read",fallback:{method:"GET",url:"/1/indexes/*",body:{params:u}},callback:n})},n.prototype.setSecurityTags=function(e){if("[object Array]"===Object.prototype.toString.call(e)){for(var t=[],r=0;r<e.length;++r)if("[object Array]"===Object.prototype.toString.call(e[r])){for(var n=[],o=0;o<e[r].length;++o)n.push(e[r][o]);t.push("("+n.join(",")+")")}else t.push(e[r]);e=t.join(",")}this.securityTags=e},n.prototype.setUserToken=function(e){this.userToken=e},n.prototype.clearCache=function(){this.cache={}},n.prototype.setRequestTimeout=function(e){e&&(this._timeouts.connect=this._timeouts.read=this._timeouts.write=e)},n.prototype.setTimeouts=function(e){this._timeouts=e},n.prototype.getTimeouts=function(){return this._timeouts},n.prototype._getAppIdData=function(){var e=p.get(this.applicationID);return null!==e&&this._cacheAppIdData(e),e},n.prototype._setAppIdData=function(e){return e.lastChange=(new Date).getTime(),this._cacheAppIdData(e),p.set(this.applicationID,e)},n.prototype._checkAppIdData=function(){var e=this._getAppIdData(),t=(new Date).getTime();return null===e||t-e.lastChange>d?this._resetInitialAppIdData(e):e},n.prototype._resetInitialAppIdData=function(e){var t=e||{};return t.hostIndexes={read:0,write:0},t.timeoutMultiplier=1,t.shuffleResult=t.shuffleResult||s([1,2,3]),this._setAppIdData(t)},n.prototype._cacheAppIdData=function(e){this._hostIndexes=e.hostIndexes,this._timeoutMultiplier=e.timeoutMultiplier,this._shuffleResult=e.shuffleResult},n.prototype._partialAppIdDataUpdate=function(t){var r=e(5),n=this._getAppIdData();return r(t,function(e,t){n[t]=e}),this._setAppIdData(n)},n.prototype._getHostByType=function(e){return this.hosts[e][this._getHostIndexByType(e)]},n.prototype._getTimeoutMultiplier=function(){return this._timeoutMultiplier},n.prototype._getHostIndexByType=function(e){return this._hostIndexes[e]},n.prototype._setHostIndexByType=function(t,r){var n=e(25),o=n(this._hostIndexes);return o[r]=t,this._partialAppIdDataUpdate({hostIndexes:o}),t},n.prototype._incrementHostIndex=function(e){return this._setHostIndexByType((this._getHostIndexByType(e)+1)%this.hosts[e].length,e)},n.prototype._incrementTimeoutMultipler=function(){var e=Math.max(this._timeoutMultiplier+1,4);return this._partialAppIdDataUpdate({timeoutMultiplier:e})},n.prototype._getTimeoutsForRequest=function(e){return{connect:this._timeouts.connect*this._timeoutMultiplier,complete:this._timeouts[e]*this._timeoutMultiplier}}}).call(this,e(12))},{1:1,12:12,18:18,25:25,28:28,29:29,30:30,34:34,5:5,8:8}],16:[function(e,t,r){function n(){i.apply(this,arguments)}var o=e(7),i=e(18),s=e(26),a=e(27),c=e(29),u=e(28);t.exports=n,o(n,i),n.prototype.addObject=function(e,t,r){var n=this;return 1!==arguments.length&&"function"!=typeof t||(r=t,t=void 0),this.as._jsonRequest({method:void 0!==t?"PUT":"POST",url:"/1/indexes/"+encodeURIComponent(n.indexName)+(void 0!==t?"/"+encodeURIComponent(t):""),body:e,hostType:"write",callback:r})},n.prototype.addObjects=function(t,r){var n=e(8),o="Usage: index.addObjects(arrayOfObjects[, callback])";if(!n(t))throw new Error(o);for(var i=this,s={requests:[]},a=0;a<t.length;++a){var c={action:"addObject",body:t[a]};s.requests.push(c)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(i.indexName)+"/batch",body:s,hostType:"write",callback:r})},n.prototype.partialUpdateObject=function(e,t,r){1!==arguments.length&&"function"!=typeof t||(r=t,t=void 0);var n=this,o="/1/indexes/"+encodeURIComponent(n.indexName)+"/"+encodeURIComponent(e.objectID)+"/partial";return t===!1&&(o+="?createIfNotExists=false"),this.as._jsonRequest({method:"POST",url:o,body:e,hostType:"write",callback:r})},n.prototype.partialUpdateObjects=function(t,r){var n=e(8),o="Usage: index.partialUpdateObjects(arrayOfObjects[, callback])";if(!n(t))throw new Error(o);for(var i=this,s={requests:[]},a=0;a<t.length;++a){var c={action:"partialUpdateObject",objectID:t[a].objectID,body:t[a]};s.requests.push(c)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(i.indexName)+"/batch",body:s,hostType:"write",callback:r})},n.prototype.saveObject=function(e,t){var r=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(r.indexName)+"/"+encodeURIComponent(e.objectID),body:e,hostType:"write",callback:t})},n.prototype.saveObjects=function(t,r){var n=e(8),o="Usage: index.saveObjects(arrayOfObjects[, callback])";if(!n(t))throw new Error(o);for(var i=this,s={requests:[]},a=0;a<t.length;++a){var c={action:"updateObject",objectID:t[a].objectID,body:t[a]};s.requests.push(c)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(i.indexName)+"/batch",body:s,hostType:"write",callback:r})},n.prototype.deleteObject=function(e,t){if("function"==typeof e||"string"!=typeof e&&"number"!=typeof e){var r=new u.AlgoliaSearchError("Cannot delete an object without an objectID");return t=e,"function"==typeof t?t(r):this.as._promise.reject(r)}var n=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(n.indexName)+"/"+encodeURIComponent(e),hostType:"write",callback:t})},n.prototype.deleteObjects=function(t,r){var n=e(8),o=e(30),i="Usage: index.deleteObjects(arrayOfObjectIDs[, callback])";if(!n(t))throw new Error(i);var s=this,a={requests:o(t,function(e){return{action:"deleteObject",objectID:e,body:{objectID:e}}})};return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(s.indexName)+"/batch",body:a,hostType:"write",callback:r})},n.prototype.deleteByQuery=function(t,r,n){function o(e){if(0===e.nbHits)return e;var t=p(e.hits,function(e){return e.objectID});return h.deleteObjects(t).then(i).then(s)}function i(e){return h.waitTask(e.taskID)}function s(){return h.deleteByQuery(t,r)}function a(){c(function(){n(null)},d._setTimeout||setTimeout)}function u(e){c(function(){n(e)},d._setTimeout||setTimeout)}var l=e(25),p=e(30),h=this,d=h.as;1===arguments.length||"function"==typeof r?(n=r,r={}):r=l(r),r.attributesToRetrieve="objectID",r.hitsPerPage=1e3,r.distinct=!1,this.clearCache();var f=this.search(t,r).then(o);return n?void f.then(a,u):f},n.prototype.browseAll=function(t,r){function n(e){if(!a._stopped){var t;t=void 0!==e?"cursor="+encodeURIComponent(e):l,c._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(u.indexName)+"/browse?"+t,hostType:"read",callback:o})}}function o(e,t){if(!a._stopped)return e?void a._error(e):(a._result(t),void 0===t.cursor?void a._end():void n(t.cursor))}"object"==typeof t&&(r=t,t=void 0);var i=e(31),s=e(17),a=new s,c=this.as,u=this,l=c._getSearchParams(i({},r||{},{query:t}),"");return n(),a},n.prototype.ttAdapter=function(e){var t=this;return function(r,n,o){var i;i="function"==typeof o?o:n,t.search(r,e,function(e,t){return e?void i(e):void i(t.hits)})}},n.prototype.waitTask=function(e,t){function r(){return l._jsonRequest({method:"GET",hostType:"read",url:"/1/indexes/"+encodeURIComponent(u.indexName)+"/task/"+e}).then(function(e){a++;var t=i*a*a;return t>s&&(t=s),"published"!==e.status?l._promise.delay(t).then(r):e})}function n(e){c(function(){t(null,e)},l._setTimeout||setTimeout)}function o(e){c(function(){t(e)},l._setTimeout||setTimeout)}var i=100,s=5e3,a=0,u=this,l=u.as,p=r();return t?void p.then(n,o):p},n.prototype.clearIndex=function(e){var t=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(t.indexName)+"/clear",hostType:"write",callback:e})},n.prototype.getSettings=function(e){var t=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(t.indexName)+"/settings?getVersion=2",hostType:"read",callback:e})},n.prototype.searchSynonyms=function(e,t){return"function"==typeof e?(t=e,e={}):void 0===e&&(e={}),this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/search",body:e,hostType:"read",callback:t})},n.prototype.saveSynonym=function(e,t,r){return"function"==typeof t?(r=t,t={}):void 0===t&&(t={}),this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/"+encodeURIComponent(e.objectID)+"?forwardToSlaves="+(t.forwardToSlaves?"true":"false"),body:e,hostType:"write",callback:r})},n.prototype.getSynonym=function(e,t){return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/"+encodeURIComponent(e),hostType:"read",callback:t})},n.prototype.deleteSynonym=function(e,t,r){return"function"==typeof t?(r=t,t={}):void 0===t&&(t={}),this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/"+encodeURIComponent(e)+"?forwardToSlaves="+(t.forwardToSlaves?"true":"false"),hostType:"write",callback:r})},n.prototype.clearSynonyms=function(e,t){return"function"==typeof e?(t=e,e={}):void 0===e&&(e={}),this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/clear?forwardToSlaves="+(e.forwardToSlaves?"true":"false"),hostType:"write",callback:t})},n.prototype.batchSynonyms=function(e,t,r){return"function"==typeof t?(r=t,t={}):void 0===t&&(t={}),this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/batch?forwardToSlaves="+(t.forwardToSlaves?"true":"false")+"&replaceExistingSynonyms="+(t.replaceExistingSynonyms?"true":"false"),hostType:"write",body:e,callback:r})},n.prototype.setSettings=function(e,t,r){1!==arguments.length&&"function"!=typeof t||(r=t,t={});var n=t.forwardToSlaves||!1,o=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(o.indexName)+"/settings?forwardToSlaves="+(n?"true":"false"),hostType:"write",body:e,callback:r})},n.prototype.listUserKeys=function(e){var t=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(t.indexName)+"/keys",hostType:"read",callback:e})},n.prototype.getUserKeyACL=function(e,t){var r=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(r.indexName)+"/keys/"+e,hostType:"read",callback:t})},n.prototype.deleteUserKey=function(e,t){var r=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(r.indexName)+"/keys/"+e,hostType:"writ
32278e",callback:t})},n.prototype.addUserKey=function(t,r,n){var o=e(8),i="Usage: index.addUserKey(arrayOfAcls[, params, callback])";if(!o(t))throw new Error(i);1!==arguments.length&&"function"!=typeof r||(n=r,r=null);var s={acl:t};return r&&(s.validity=r.validity,s.maxQueriesPerIPPerHour=r.maxQueriesPerIPPerHour,s.maxHitsPerQuery=r.maxHitsPerQuery,s.description=r.description,r.queryParameters&&(s.queryParameters=this.as._getSearchParams(r.queryParameters,"")),s.referers=r.referers),this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/keys",body:s,hostType:"write",callback:n})},n.prototype.addUserKeyWithValidity=s(function(e,t,r){return this.addUserKey(e,t,r)},a("index.addUserKeyWithValidity()","index.addUserKey()")),n.prototype.updateUserKey=function(t,r,n,o){var i=e(8),s="Usage: index.updateUserKey(key, arrayOfAcls[, params, callback])";if(!i(r))throw new Error(s);2!==arguments.length&&"function"!=typeof n||(o=n,n=null);var a={acl:r};return n&&(a.validity=n.validity,a.maxQueriesPerIPPerHour=n.maxQueriesPerIPPerHour,a.maxHitsPerQuery=n.maxHitsPerQuery,a.description=n.description,n.queryParameters&&(a.queryParameters=this.as._getSearchParams(n.queryParameters,"")),a.referers=n.referers),this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/keys/"+t,body:a,hostType:"write",callback:o})}},{17:17,18:18,25:25,26:26,27:27,28:28,29:29,30:30,31:31,7:7,8:8}],17:[function(e,t,r){"use strict";function n(){}t.exports=n;var o=e(7),i=e(4).EventEmitter;o(n,i),n.prototype.stop=function(){this._stopped=!0,this._clean()},n.prototype._end=function(){this.emit("end"),this._clean()},n.prototype._error=function(e){this.emit("error",e),this._clean()},n.prototype._result=function(e){this.emit("result",e)},n.prototype._clean=function(){this.removeAllListeners("stop"),this.removeAllListeners("end"),this.removeAllListeners("error"),this.removeAllListeners("result")}},{4:4,7:7}],18:[function(e,t,r){function n(e,t){this.indexName=t,this.as=e,this.typeAheadArgs=null,this.typeAheadValueOption=null,this.cache={}}var o=e(24),i=e(26),s=e(27);t.exports=n,n.prototype.clearCache=function(){this.cache={}},n.prototype.search=o("query"),n.prototype.similarSearch=o("similarQuery"),n.prototype.browse=function(t,r,n){var o,i,s=e(31),a=this;0===arguments.length||1===arguments.length&&"function"==typeof arguments[0]?(o=0,n=arguments[0],t=void 0):"number"==typeof arguments[0]?(o=arguments[0],"number"==typeof arguments[1]?i=arguments[1]:"function"==typeof arguments[1]&&(n=arguments[1],i=void 0),t=void 0,r=void 0):"object"==typeof arguments[0]?("function"==typeof arguments[1]&&(n=arguments[1]),r=arguments[0],t=void 0):"string"==typeof arguments[0]&&"function"==typeof arguments[1]&&(n=arguments[1],r=void 0),r=s({},r||{},{page:o,hitsPerPage:i,query:t});var c=this.as._getSearchParams(r,"");return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(a.indexName)+"/browse?"+c,hostType:"read",callback:n})},n.prototype.browseFrom=function(e,t){return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/browse?cursor="+encodeURIComponent(e),hostType:"read",callback:t})},n.prototype.searchForFacetValues=function(t,r){var n=e(25),o=e(32),i="Usage: index.searchForFacetValues({facetName, facetQuery, ...params}[, callback])";if(void 0===t.facetName||void 0===t.facetQuery)throw new Error(i);var s=t.facetName,a=o(n(t),function(e){return"facetName"===e}),c=this.as._getSearchParams(a,"");return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/facets/"+encodeURIComponent(s)+"/query",hostType:"read",body:{params:c},callback:r})},n.prototype.searchFacet=i(function(e,t){return this.searchForFacetValues(e,t)},s("index.searchFacet(params[, callback])","index.searchForFacetValues(params[, callback])")),n.prototype._search=function(e,t,r){return this.as._jsonRequest({cache:this.cache,method:"POST",url:t||"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:{params:e},hostType:"read",fallback:{method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName),body:{params:e}},callback:r})},n.prototype.getObject=function(e,t,r){var n=this;1!==arguments.length&&"function"!=typeof t||(r=t,t=void 0);var o="";if(void 0!==t){o="?attributes=";for(var i=0;i<t.length;++i)0!==i&&(o+=","),o+=t[i]}return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(n.indexName)+"/"+encodeURIComponent(e)+o,hostType:"read",callback:r})},n.prototype.getObjects=function(t,r,n){var o=e(8),i=e(30),s="Usage: index.getObjects(arrayOfObjectIDs[, callback])";if(!o(t))throw new Error(s);var a=this;1!==arguments.length&&"function"!=typeof r||(n=r,r=void 0);var c={requests:i(t,function(e){var t={indexName:a.indexName,objectID:e};return r&&(t.attributesToRetrieve=r.join(",")),t})};return this.as._jsonRequest({method:"POST",url:"/1/indexes/*/objects",hostType:"read",body:c,callback:n})},n.prototype.as=null,n.prototype.indexName=null,n.prototype.typeAheadArgs=null,n.prototype.typeAheadValueOption=null},{24:24,25:25,26:26,27:27,30:30,31:31,32:32,8:8}],19:[function(e,t,r){"use strict";var n=e(14),o=e(20);t.exports=o(n)},{14:14,20:20}],20:[function(e,t,r){(function(r){"use strict";var n=e(6),o=n.Promise||e(3).Promise;t.exports=function(t,i){function s(t,r,n){var o=e(25),i=e(21);return n=o(n||{}),void 0===n.protocol&&(n.protocol=i()),n._ua=n._ua||s.ua,new a(t,r,n)}function a(){t.apply(this,arguments)}var c=e(7),u=e(28),l=e(22),p=e(23),h=e(33);i=i||"","debug"===r.env.NODE_ENV&&e(1).enable("algoliasearch*"),s.version=e(35),s.ua="Algolia for vanilla JavaScript "+i+s.version,s.initPlaces=h(s),n.__algolia={debug:e(1),algoliasearch:s};var d={hasXMLHttpRequest:"XMLHttpRequest"in n,hasXDomainRequest:"XDomainRequest"in n};return d.hasXMLHttpRequest&&(d.cors="withCredentials"in new XMLHttpRequest),c(a,t),a.prototype._request=function(e,t){return new o(function(r,n){function o(){if(!f){clearTimeout(h);var e;try{e={body:JSON.parse(m.responseText),responseText:m.responseText,statusCode:m.status,headers:m.getAllResponseHeaders&&m.getAllResponseHeaders()||{}}}catch(t){e=new u.UnparsableJSON({more:m.responseText})}e instanceof u.UnparsableJSON?n(e):r(e)}}function i(e){f||(clearTimeout(h),n(new u.Network({more:e})))}function s(){f=!0,m.abort(),n(new u.RequestTimeout)}
32278function a(){v=!0,clearTimeout(h),h=setTimeout(s,t.timeouts.complete)}function c(){v||a()}function p(){!v&&m.readyState>1&&a()}if(!d.cors&&!d.hasXDomainRequest)return void n(new u.Network("CORS not supported"));e=l(e,t.headers);var h,f,y=t.body,m=d.cors?new XMLHttpRequest:new XDomainRequest,v=!1;h=setTimeout(s,t.timeouts.connect),m.onprogress=c,"onreadystatechange"in m&&(m.onreadystatechange=p),m.onload=o,m.onerror=i,m instanceof XMLHttpRequest?m.open(t.method,e,!0):m.open(t.method,e),d.cors&&(y&&("POST"===t.method?m.setRequestHeader("content-type","application/x-www-form-urlencoded"):m.setRequestHeader("content-type","application/json")),m.setRequestHeader("accept","application/json")),m.send(y)})},a.prototype._request.fallback=function(e,t){return e=l(e,t.headers),new o(function(r,n){p(e,t,function(e,t){return e?void n(e):void r(t)})})},a.prototype._promise={reject:function(e){return o.reject(e)},resolve:function(e){return o.resolve(e)},delay:function(e){return new o(function(t){setTimeout(t,e)})}},s}}).call(this,e(12))},{1:1,12:12,21:21,22:22,23:23,25:25,28:28,3:3,33:33,35:35,6:6,7:7}],21:[function(e,t,r){"use strict";function n(){var e=window.document.location.protocol;return"http:"!==e&&"https:"!==e&&(e="http:"),e}t.exports=n},{}],22:[function(e,t,r){"use strict";function n(e,t){return e+=/\?/.test(e)?"&":"?",e+o(t)}t.exports=n;var o=e(13)},{13:13}],23:[function(e,t,r){"use strict";function n(e,t,r){function n(){t.debug("JSONP: success"),m||h||(m=!0,p||(t.debug("JSONP: Fail. Script loaded but did not call the callback"),a(),r(new o.JSONPScriptFail)))}function s(){"loaded"!==this.readyState&&"complete"!==this.readyState||n()}function a(){clearTimeout(v),f.onload=null,f.onreadystatechange=null,f.onerror=null,d.removeChild(f)}function c(){try{delete window[y],delete window[y+"_loaded"]}catch(e){window[y]=window[y+"_loaded"]=void 0}}function u(){t.debug("JSONP: Script timeout"),h=!0,a(),r(new o.RequestTimeout)}function l(){t.debug("JSONP: Script error"),m||h||(a(),r(new o.JSONPScriptError))}if("GET"!==t.method)return void r(new Error("Method "+t.method+" "+e+" is not supported by JSONP."));t.debug("JSONP: start");var p=!1,h=!1;i+=1;var d=document.getElementsByTagName("head")[0],f=document.createElement("script"),y="algoliaJSONP_"+i,m=!1;window[y]=function(e){return c(),h?void t.debug("JSONP: Late answer, ignoring"):(p=!0,a(),void r(null,{body:e}))},e+="&callback="+y,t.jsonBody&&t.jsonBody.params&&(e+="&"+t.jsonBody.params);var v=setTimeout(u,t.timeouts.complete);f.onreadystatechange=s,f.onload=n,f.onerror=l,f.async=!0,f.defer=!0,f.src=e,d.appendChild(f)}t.exports=n;var o=e(28),i=0},{28:28}],24:[function(e,t,r){function n(e,t){return function(r,n,i){if("function"==typeof r&&"object"==typeof n||"object"==typeof i)throw new o.AlgoliaSearchError("index.search usage is index.search(query, params, cb)");0===arguments.length||"function"==typeof r?(i=r,r=""):1!==arguments.length&&"function"!=typeof n||(i=n,n=void 0),"object"==typeof r&&null!==r?(n=r,r=void 0):void 0!==r&&null!==r||(r="");var s="";return void 0!==r&&(s+=e+"="+encodeURIComponent(r)),void 0!==n&&(s=this.as._getSearchParams(n,s)),this._search(s,t,i)}}t.exports=n;var o=e(28)},{28:28}],25:[function(e,t,r){t.exports=function(e){return JSON.parse(JSON.stringify(e))}},{}],26:[function(e,t,r){t.exports=function(e,t){function r(){return n||(console.log(t),n=!0),e.apply(this,arguments)}var n=!1;return r}},{}],27:[function(e,t,r){t.exports=function(e,t){var r=e.toLowerCase().replace(".","").replace("()","");return"algoliasearch: `"+e+"` was replaced by `"+t+"`. Please see https://github.com/algolia/algoliasearch-client-js/wiki/Deprecated#"+r}},{}],28:[function(e,t,r){"use strict";function n(t,r){var n=e(5),o=this;"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):o.stack=(new Error).stack||"Cannot get a stacktrace, browser is too old",this.name="AlgoliaSearchError",this.message=t||"Unknown error",r&&n(r,function(e,t){o[t]=e})}function o(e,t){function r(){var r=Array.prototype.slice.call(arguments,0);"string"!=typeof r[0]&&r.unshift(t),n.apply(this,r),this.name="AlgoliaSearch"+e+"Error"}return i(r,n),r}var i=e(7);
32278i(n,Error),t.exports={AlgoliaSearchError:n,UnparsableJSON:o("UnparsableJSON","Could not parse the incoming response as JSON, see err.more for details"),RequestTimeout:o("RequestTimeout","Request timedout before getting a response"),Network:o("Network","Network issue, see err.more for details"),JSONPScriptFail:o("JSONPScriptFail","<script> was loaded but did not call our provided callback"),JSONPScriptError:o("JSONPScriptError","<script> unable to load due to an `error` event on it"),Unknown:o("Unknown","Unknown error occured")}},{5:5,7:7}],29:[function(e,t,r){t.exports=function(e,t){t(e,0)}},{}],30:[function(e,t,r){var n=e(5);t.exports=function(e,t){var r=[];return n(e,function(n,o){r.push(t(n,o,e))}),r}},{5:5}],31:[function(e,t,r){var n=e(5);t.exports=function o(e){var t=Array.prototype.slice.call(arguments);return n(t,function(t){for(var r in t)t.hasOwnProperty(r)&&("object"==typeof e[r]&&"object"==typeof t[r]?e[r]=o({},e[r],t[r]):void 0!==t[r]&&(e[r]=t[r]))}),e}},{5:5}],32:[function(e,t,r){t.exports=function(t,r){var n=e(10),o=e(5),i={};return o(n(t),function(e){r(e)!==!0&&(i[e]=t[e])}),i}},{10:10,5:5}],33:[function(e,t,r){function n(t){return function(r,n,i){var s=e(25);i=i&&s(i)||{},i.hosts=i.hosts||["places-dsn.algolia.net","places-1.algolianet.com","places-2.algolianet.com","places-3.algolianet.com"],0!==arguments.length&&"object"!=typeof r&&void 0!==r||(r="",n="",i._allowEmptyCredentials=!0);var a=t(r,n,i),c=a.initIndex("places");return c.search=o("query","/1/places/query"),c}}t.exports=n;var o=e(24)},{24:24,25:25}],34:[function(e,t,r){(function(r){function n(e,t){return 1===arguments.length?s.get(e):s.set(e,t)}function o(){try{return"localStorage"in r&&null!==r.localStorage&&!r.localStorage[c]&&(r.localStorage.setItem(c,JSON.stringify({})),!0)}catch(e){return!1}}function i(){try{r.localStorage.removeItem(c)}catch(e){}}var s,a=e(1)("algoliasearch:src/hostIndexState.js"),c="algoliasearch-client-js",u={state:{},set:function(e,t){return this.state[e]=t,this.state[e]},get:function(e){return this.state[e]||null}},l={set:function(e,t){try{var n=JSON.parse(r.localStorage[c]);return n[e]=t,r.localStorage[c]=JSON.stringify(n),n[e]}catch(o){return a("localStorage set failed with",o),i(),s=u,s.set(e,t)}},get:function(e){return JSON.parse(r.localStorage[c])[e]||null}};s=o()?l:u,t.exports={get:n,set:n}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{1:1}],35:[function(e,t,r){"use strict";t.exports="3.20.3"},{}]},{},[19])(19)});
32279// This is a manifest file that'll be compiled into application.js, which will include all the files
32280// listed below.
32281//
32282// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
32283// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
32284//
32285// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
32286// compiled file. JavaScript code in this file should be added after the last require_* statement.
32287//
32288// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
32289// about supported directives.
32290//
32291
32292
32293
32294
32295
32296
32297
32298
32299
32300
32301
32302
32303
32304
32305function start_theme() {
32306
32307  $('input[id^=theme_colour_header_back]').change(function(){
32308    refresh_header_theme();
32309  })
32310
32311  $('input#header_links_buttons').change(function(){
32312    if ($('input#header_links_buttons').is(':checked')) {
32313      $('div#colour_menu_text').hide();
32314      $('div#colour_menu_back').show();
32315    } else {
32316      $('div#colour_menu_text').show();
32317      $('div#colour_menu_back').hide();
32318    }
32319    refresh_header_theme();
32320  })
32321
32322  if ($('input#header_links_buttons').is(':checked')) {
32323    $('div#colour_menu_text').hide();
32324  } else {
32325    $('div#colour_menu_back').hide();
32326  }
32327
32328  refresh_header_theme();
32329
32330}
32331
32332function refresh_header_theme() {
32333
32334  if ($('input[id^=theme_colour_header_back]:checked').val() == '#FFFFFF') {
32335    $('input[id^=theme_colour_header_text]').val('#4C4C4C');
32336    if ($('input#header_links_buttons').is(':checked')) {
32337      $set = false;
32338      $('input[id^=theme_colour_menu_back]').each(function(){
32339        $(this).prop('disabled', false);
32340        if ($(this).val() != $('input[id^=theme_colour_header_back]:checked').val()) {
32341          if (!$set) {
32342            if ($('input[id^=theme_colour_menu_text]:checked').val() != $('input[id^=theme_colour_header_back]:checked').val()) {
32343              if ($(this).val() == $('input[id^=theme_colour_menu_text]:checked').val()) {
32344                $(this).prop('checked', true);
32345                $set = true;
32346              }
32347            }
32348          }
32349        } else {
32350          $(this).prop('disabled', true);
32351        }
32352      });
32353      $('input[id^=theme_colour_menu_text]').each(function(){
32354        $(this).prop('disabled', false);
32355        if ($(this).val() == '#FFFFFF') {
32356          $(this).prop('checked', true);
32357        } else {
32358          $(this).prop('disabled', true);
32359        }
32360      });
32361    } else {
32362      $set = false;
32363      $('input[id^=theme_colour_menu_text]').each(function(){
32364        $(this).prop('disabled', false);
32365        if ($(this).val() != $('input[id^=theme_colour_header_back]:checked').val()) {
32366          if (!$set) {
32367            if ($('input[id^=theme_colour_menu_back]:checked').val() != $('input[id^=theme_colour_header_back]:checked').val()) {
32368              if ($(this).val() == $('input[id^=theme_colour_menu_back]:checked').val()) {
32369                $(this).prop('checked', true);
32370                $set = true;
32371              }
32372            }
32373          }
32374        } else {
32375          $(this).prop('disabled', true);
32376        }
32377      });
32378      $('input[id^=theme_colour_menu_back]').each(function(){
32379        $(this).prop('disabled', false);
32380        if ($(this).val() == '#FFFFFF') {
32381          $(this).prop('checked', true);
32382        } else {
32383          $(this).prop('disabled', true);
32384        }
32385      });
32386    }
32387  } else {
32388    $('input[id^=theme_colour_header_text]').val('#FFFFFF');
32389    if ($('input#header_links_buttons').is(':checked')) {
32390      $('input[id^=theme_colour_menu_text]').each(function(){
32391        $(this).prop('disabled', false);
32392        if ($(this).val() == $('input[id^=theme_colour_header_back]:checked').val()) {
32393          $(this).prop('checked', true);
32394        } else {
32395          $(this).prop('disabled', true);
32396        }
32397      });
32398      $('input[id^=theme_colour_menu_back]').each(function(){
32399        $(this).prop('disabled', false);
32400        if ($(this).val() == '#FFFFFF') {
32401          $(this).prop('checked', true);
32402        } else {
32403          $(this).prop('disabled', true);
32404        }
32405      });
32406    } else {
32407      $('input[id^=theme_colour_menu_text]').each(function(){
32408        $(this).prop('disabled', false);
32409        if ($(this).val() == '#FFFFFF') {
32410          $(this).prop('checked', true);
32411        } else {
32412          $(this).prop('disabled', true);
32413        }
32414      });
32415      $('input[id^=theme_colour_menu_back]').each(function(){
32416        $(this).prop('disabled', false);
32417        if ($(this).val() == $('input[id^=theme_colour_header_back]:checked').val()) {
32418          $(this).prop('checked', true);
32419        } else {
32420          $(this).prop('disabled', true);
32421        }
32422      });
32423    }
32424  }
32425
32426}
32427
32428function start_fonts() {
32429
32430  refresh_fonts();
32431
32432  $preferred_fonts = [
32433    'Libre Baskerville',
32434    'Montserrat',
32435    'Oswald',
32436    'Raleway',
32437    'Roboto Slab',
32438    'Rubik',
32439    'Signika',
32440    'Ubuntu',
32441    'Volkhov'
32442  ];
32443  $('select[id^=theme_font] option').each(function(){
32444    $(this).addClass('expanded');
32445    if ($preferred_fonts.indexOf($(this).html())>-1) {
32446      $(this).addClass('standard');
32447    }
32448  });
32449  $('select[id^=theme_font]').parents('div.field').each(function(){
32450    $(this).addClass('fonts');
32451    if ($preferred_fonts.indexOf($('option:selected', $(this)).html())>-1) {
32452      $('a.toggle.standard', $(this)).addClass('selected');
32453    } else {
32454      $('select', $(this)).addClass('expanded');
32455      $('a.toggle.expanded', $(this)).addClass('selected');
32456    }
32457    $('a.toggle', $(this)).click(function(e){
32458      $('a.toggle', $(this).parents('div.field')).removeClass('selected');
32459      $(this).addClass('selected');
32460      if ($(this).hasClass('expanded')) {
32461        $('select', $(this).parents('div.field')).addClass('expanded');
32462      } else {
32463        $('select', $(this).parents('div.field')).removeClass('expanded');
32464      }
32465      e.preventDefault();
32466    });
32467  });
32468
32469  $('select[id^=theme_font]').change(function(){
32470    $('head').append($('<link href="https://fonts.googleapis.com/css?family='+$(this).val().replace(' ','+')+':400,400i,700,700i" rel="stylesheet" />'));
32471    refresh_fonts();
32472  });
32473
32474}
32475
32476function refresh_fonts() {
32477
32478  var $trim_length = 15;
32479
32480  $font_1_name = $('select#theme_font_1').val();
32481  $font_1_trim = ($font_1_name.length > ($trim_length+3)) ? $font_1_name.substring(0, $trim_length)+'...' : $font_1_name;
32482//  $font_2_name = $('select#theme_font_2').val();
32483//  $font_2_trim = ($font_2_name.length > ($trim_length+3)) ? $font_2_name.substring(0, $trim_length)+'...' : $font_2_name;
32484//  $font_3_name = $('select#theme_font_3').val();
32485//  $font_3_trim = ($font_3_name.length > ($trim_length+3)) ? $font_3_name.substring(0, $trim_length)+'...' : $font_3_name;
32486  $font_4_name = $('input#theme_font_4').val();
32487  $font_4_trim = ($font_4_name.length > ($trim_length+3)) ? $font_4_name.substring(0, $trim_length)+'...' : $font_4_name;
32488
32489//  $font_1_trim = $font_1_name.val().trim().substring(0, 20).split(" ").slice(0, -1).join(" ") + "...";
32490
32491  $('label.font_1').html($font_1_trim);
32492//  $('label.font_2').html($font_2_trim);
32493//  $('label.font_3').html($font_3_trim);
32494  $('label.font_4').html($font_4_trim);
32495  $('input.font_1').val($font_1_name);
32496//  $('input.font_2').val($font_2_name);
32497//  $('input.font_3').val($font_3_name);
32498  $('input.font_4').val($font_4_name);
32499  $('option.font_1').val($font_1_name);
32500//  $('option.font_2').val($font_2_name);
32501//  $('option.font_3').val($font_3_name);
32502  $('option.font_4').val($font_4_name);
32503
32504  refresh_preview();
32505
32506}
32507
32508function start_colours() {
32509
32510  refresh_colours();
32511
32512  $('div').click(refresh_colours);
32513
32514  $('input.colour.hex').change(refresh_colours);
32515
32516}
32517
32518function refresh_colours() {
32519
32520  $('input.colour_1').val($('input#theme_colour_1').val());
32521  $('input.colour_2').val($('input#theme_colour_2').val());
32522  $('input.colour_3').val($('input#theme_colour_3').val());
32523  $('input.colour_4').val($('input#theme_colour_4').val());
32524  $('option.colour_1').val($('input#theme_colour_1').val());
32525  $('option.colour_2').val($('input#theme_colour_2').val());
32526  $('option.colour_3').val($('input#theme_colour_3').val());
32527  $('option.colour_4').val($('input#theme_colour_4').val());
32528
32529  $('label.colour').each(function(){
32530    $(this).css('color', '#FFFFFF');
32531    $(this).css('background-color', $('#'+$(this).attr('for')).val());
32532  });
32533
32534  $('input[type="hidden"]').each(function(){
32535    var $back = $('input[name="'+$(this).attr('name').replace('text','back')+'"]:checked').val();
32536  });
32537
32538  refresh_preview();
32539
32540}
32541
32542function start_images() {
32543
32544  $('div.row.image').each(function(){
32545    $('input[id*=remove_]', $(this)).each(function(){
32546      $(this).change(function(){
32547        if (this.checked) {
32548          $('div.thumb', $(this).parents('div.row.image')).hide();
32549          $('div.field', $(this).parents('div.row.image')).show();
32550        } else {
32551          $('div.field', $(this).parents('div.row.image')).hide();
32552          $('div.thumb', $(this).parents('div.row.image')).show();
32553        }
32554      });
32555      $('input#'+$('label', $(this).parents('div.row.image')).attr('for')).change(function(){
32556        if (this.files.length == 0) {
32557          $('input[id*=remove_]', $(this).parents('div.row.image')).prop('checked', true);
32558        } else {
32559          $('input[id*=remove_]', $(this).parents('div.row.image')).prop('checked', false);
32560        }
32561      });
32562      $(this).hide();
32563      $('div.field', $(this).parents('div.row.image')).hide();
32564    });
32565  });
32566
32567}
32568
32569function refresh_template(id, content) {
32570
32571  $('#'+id).html(content);
32572
32573}
32574
32575function start_editor() {
32576
32577  $('textarea.editor').each(function(){
32578
32579    $(this).hide();
32580
32581    $toolbar = $('<div class="toolbar"></div>');
32582    $iframe = $('<iframe id="editor1" class="editor"></iframe>');
32583
32584    $editor = $('<div class="editor"></div>');
32585    $editor.append($toolbar);
32586    $editor.append($iframe);
32587
32588    $(this).parent().append($editor);
32589
32590    refresh_toolbar();
32591
32592    $contents = $(this).val();
32593    $iframe.contents().get(0).write($contents);
32594    $iframe.contents().get(0).close();
32595    $iframe.contents().find('body').addClass('editor');
32596    $('head link').each(function(e){
32597      $iframe.contents().find('head').append($(this).clone());
32598    });
32599    $('head style').each(function(e){
32600      $iframe.contents().find('head').append($(this).clone());
32601    });
32602    $iframe.contents().attr('designMode', 'on');
32603
32604    $iframe.contents().find('body').keyup(function(e){
32605      refresh_toolbar();
32606    });
32607    $iframe.contents().find('body').mouseup(function(e){
32608      refresh_toolbar();
32609    });
32610
32611    $(this).parents('form').submit(function(e){
32612      $('textarea.editor').val($iframe.contents().find('body').html());
32613    });
32614
32615  });
32616
32617  $('textarea.editor-mailer').redactor({
32618    structure: true,
32619    minHeight: 300,
32620    maxHeight: 600,
32621    imageUpload: '/redactor2_rails/images',
32622    imageUploadFields: '[name="authenticity_token"]',
32623    imageCaption: true,
32624    imagePosition: true,
32625    imageResizable: true,
32626    imageFloatMargin: '0',
32627    linkify: false,
32628    linkValidation: false,
32629    plugins: [
32630      'source',
32631      'alignment',
32632//      'hubblebutton',
32633      'hubbletoken',
32634      'imagemanager'
32635    ],
32636    buttons: [
32637      'format',
32638      'lists',
32639      'bold',
32640      'italic',
32641//      'deleted',
32642      'horizontalrule',
32643      'image',
32644      'link'
32645    ],
32646    formatting: [
32647      'p',
32648      'blockquote',
32649      'h1',
32650      'h2',
32651      'h3',
32652      'h4'
32653    ],
32654    formattingAdd: {
32655      "notation": {
32656        title: 'Notation',
32657        api: 'module.block.format',
32658        args: [
32659          'p', 'class', 'small grey', 'toggle'
32660        ]
32661      }
32662    },
32663    pastePlainText: true,
32664    pasteImages: false,
32665    pasteLinks: false,
32666    callbacks: {
32667      imageUploadError: function(json)
32668      {
32669        console.log(json);
32670      },
32671      fileUploadError: function(json)
32672      {
32673        console.log(json);
32674      }
32675    }
32676  });
32677
32678  $('textarea.editor-complex').redactor({
32679    structure: true,
32680    minHeight: 300,
32681    maxHeight: 600,
32682    fileUpload: '/redactor2_rails/files',
32683    fileUploadFields: '[name="authenticity_token"]',
32684    imageUpload: '/redactor2_rails/images',
32685    imageUploadFields: '[name="authenticity_token"]',
32686    imageCaption: true,
32687    imagePosition: true,
32688    imageResizable: true,
32689    imageFloatMargin: '0',
32690    linkify: false,
32691    linkValidation: false,
32692    plugins: [
32693      'source',
32694      'alignment',
32695      'table',
32696      'video',
32697//      'hubblebutton',
32698      'hubblesubsup',
32699      'hubbleimage',
32700      'hubblelink',
32701      'imagemanager',
32702      'filemanager'
32703    ],
32704    buttons: [
32705      'format',
32706      'lists',
32707      'bold',
32708      'italic',
32709//      'deleted',
32710      'horizontalrule',
32711      'image',
32712      'file',
32713      'link'
32714    ],
32715    formatting: [
32716      'p',
32717      'blockquote',
32718      'h2',
32719      'h3',
32720      'h4'
32721    ],
32722    formattingAdd: {
32723      "notation": {
32724        title: 'Notation',
32725        api: 'module.block.format',
32726        args: [
32727          'p', 'class', 'small grey', 'toggle'
32728        ]
32729      }
32730    },
32731    pastePlainText: true,
32732    pasteImages: false,
32733    pasteLinks: false,
32734    callbacks: {
32735      imageUploadError: function(json)
32736      {
32737        console.log(json);
32738      },
32739      fileUploadError: function(json)
32740      {
32741        console.log(json);
32742      }
32743    }
32744  });
32745
32746  $('textarea.editor-content').redactor({
32747    structure: true,
32748    minHeight: 300,
32749    maxHeight: 600,
32750    linkify: false,
32751    linkValidation: false,
32752    plugins: [
32753      'source',
32754      'alignment',
32755      'table',
32756      'hubblesubsup',
32757      'hubbledo',
32758      'hubblelink'
32759    ],
32760    buttons: [
32761      'format',
32762      'lists',
32763      'bold',
32764      'italic',
32765      'link'
32766    ],
32767    formatting: [
32768      'p',
32769      'blockquote',
32770      'h2',
32771      'h3',
32772      'h4'
32773    ],
32774    formattingAdd: {
32775      "notation": {
32776        title: 'Notation',
32777        api: 'module.block.format',
32778        args: [
32779          'p', 'class', 'small grey', 'toggle'
32780        ]
32781      }
32782    },
32783    pastePlainText: true,
32784    pasteImages: false,
32785    pasteLinks: false
32786  });
32787
32788  $('textarea.editor-transmission').redactor({
32789    structure: true,
32790    minHeight: 300,
32791    maxHeight: 600,
32792    linkify: false,
32793    linkValidation: false,
32794    plugins: [
32795      'source',
32796      'alignment',
32797      'hubbletoken',
32798      'hubblesubsup',
32799      'hubbledo'
32800    ],
32801    buttons: [
32802      'format',
32803      'lists',
32804      'bold',
32805      'italic',
32806      'link'
32807    ],
32808    formatting: [
32809      'p',
32810      'blockquote',
32811      'h1',
32812      'h2',
32813      'h3',
32814      'h4'
32815    ],
32816    formattingAdd: {
32817      "notation": {
32818        title: 'Notation',
32819        api: 'module.block.format',
32820        args: [
32821          'p', 'class', 'small grey', 'toggle'
32822        ]
32823      }
32824    },
32825    pastePlainText: true,
32826    pasteImages: false,
32827    pasteLinks: false
32828  });
32829
32830  $('textarea.editor-grid').redactor({
32831    structure: true,
32832    minHeight: 300,
32833    maxHeight: 600,
32834    linkify: false,
32835    linkValidation: false,
32836    plugins: [
32837      'source',
32838      'alignment',
32839      'hubblesubsup',
32840      'hubbledo',
32841      'hubblelink'
32842    ],
32843    buttons: [
32844      'format',
32845      'lists',
32846      'bold',
32847      'italic',
32848      'link'
32849    ],
32850    formatting: [
32851      'p',
32852      'blockquote',
32853      'h2',
32854      'h3',
32855      'h4'
32856    ],
32857    formattingAdd: {
32858      "notation": {
32859        title: 'Notation',
32860        api: 'module.block.format',
32861        args: [
32862          'p', 'class', 'small grey', 'toggle'
32863        ]
32864      }
32865    },
32866    pastePlainText: true,
32867    pasteImages: false,
32868    pasteLinks: false
32869  });
32870
32871  $('textarea.editor-simple').redactor({
32872    structure: true,
32873    toolbar: false,
32874    minHeight: 200,
32875    maxHeight: 600,
32876    linkify: false,
32877    linkValidation: false,
32878    buttons: [
32879      'bold',
32880      'italic'
32881    ],
32882    formatting: [
32883      'p',
32884      'blockquote',
32885      'h2',
32886      'h3',
32887      'h4'
32888    ],
32889    pastePlainText: true,
32890    pasteImages: false,
32891    pasteLinks: false,
32892  });
32893
32894}
32895
32896function refresh_toolbar() {
32897
32898  $toolbar.html('');
32899
32900  $toolbar.append($('<select class="format"><option value="<p>" selected="selected">Paragraph</option><option value="<h2>">Large heading</option><option value="<h3>">Small heading</option><option value="<div>">Empty (DIV)</option></select>'));
32901  $toolbar.append($('<a href="" class="button olist"># List</a>'));
32902  $toolbar.append($('<a href="" class="button ulist">&bull; List</a>'));
32903  $toolbar.append($('<a href="" class="button bold">B</a>'));
32904  $toolbar.append($('<a href="" class="button italic">I</a>'));
32905  $toolbar.append($('<a href="" class="button link ready">Link</a>'));
32906
32907  $selection = document.getElementById('editor1').contentWindow.getSelection();
32908  $parents = [];
32909  if ($selection.rangeCount) {
32910    $ancestor = $selection.getRangeAt(0).commonAncestorContainer;
32911    while ($ancestor.parentNode.tagName != 'BODY') {
32912      $parents.push($ancestor.parentNode);
32913      $ancestor = $ancestor.parentNode;
32914    }
32915  }
32916
32917  for (var $i = 0; $i < $parents.length; $i++) {
32918    switch($parents[$i].tagName) {
32919      case 'P':
32920        $('div.toolbar select.format', $editor).val('<p>');
32921        break;
32922      case 'H2':
32923        $('div.toolbar select.format', $editor).val('<h2>');
32924        break;
32925      case 'H3':
32926        $('div.toolbar select.format', $editor).val('<h3>');
32927        break;
32928      case 'DIV':
32929        $('div.toolbar select.format', $editor).val('<div>');
32930        break;
32931      case 'OL':
32932        $('div.toolbar a.button.olist', $editor).addClass('active');
32933        break;
32934      case 'UL':
32935        $('div.toolbar a.button.ulist', $editor).addClass('active');
32936        break;
32937      case 'STRONG':
32938        $('div.toolbar a.button.bold', $editor).addClass('active');
32939        break;
32940      case 'EM':
32941        $('div.toolbar a.button.italic', $editor).addClass('active');
32942        break;
32943      case 'A':
32944        $('div.toolbar a.button.link', $editor).removeClass('ready');
32945        $('div.toolbar a.button.link', $editor).addClass('active');
32946        break;
32947    }
32948  }
32949
32950  $('div.toolbar select.format', $editor).change(function(e){
32951    $('iframe',$(this).parents('div.field')).contents().get(0).execCommand('formatBlock', false, $(this).val());
32952    refresh_toolbar();
32953    e.preventDefault();
32954  });
32955  $('div.toolbar a.button.olist', $editor).click(function(e){
32956    $('iframe',$(this).parents('div.field')).contents().get(0).execCommand('insertOrderedList',false,null);
32957    refresh_toolbar();
32958    e.preventDefault();
32959  });
32960  $('div.toolbar a.button.ulist', $editor).click(function(e){
32961    $('iframe',$(this).parents('div.field')).contents().get(0).execCommand('insertUnorderedList',false,null);
32962    refresh_toolbar();
32963    e.preventDefault();
32964  });
32965  $('div.toolbar a.button.bold', $editor).click(function(e){
32966    $('iframe',$(this).parents('div.field')).contents().get(0).execCommand('bold',fal
32966se,null);
32967    $('iframe',$(this).parents('div.field')).contents().find('b').replaceWith(function(){
32968      return $('<strong />').append($(this).contents());
32969    });
32970    refresh_toolbar();
32971    e.preventDefault();
32972  });
32973  $('div.toolbar a.button.italic', $editor).click(function(e){
32974    $('iframe',$(this).parents('div.field')).contents().get(0).execCommand('Italic',false,null);
32975    $('iframe',$(this).parents('div.field')).contents().find('i').replaceWith(function(){
32976      return $('<em />').append($(this).contents());
32977    });
32978    refresh_toolbar();
32979    e.preventDefault();
32980  });
32981  $('div.toolbar a.button.link.ready', $editor).click(function(e){
32982    $form = $('<form></form>');
32983    $form.append($('<div class="field"><label for="modal_href">URL</label><input type="text" id="modal_href" /></div>'));
32984//      $form.append($('<div class="field"><label for="modal_target">Target</label><select id="modal_target"><option value="_self" selected="selected">Same window</option><option value="_blank">New window</option></select></div>'));
32985    $form.append($('<div class="actions"><input type="submit" value="Create link" /></div>'));
32986    $form.submit(function(e){
32987      $('iframe').contents().get(0).execCommand('CreateLink',false,$('input#modal_href').val());
32988      close_modal();
32989      e.preventDefault();
32990    });
32991    start_modal($form);
32992    refresh_toolbar();
32993    e.preventDefault();
32994  });
32995  $('div.toolbar a.button.link.active', $editor).click(function(e){
32996    $('iframe',$(this).parents('div.field')).contents().get(0).execCommand('UnLink',false,null);
32997    refresh_toolbar();
32998    e.preventDefault();
32999  });
33000
33001}
33002
33003function start_menu_sort() {
33004
33005  $('form.menusort').prepend('<div class="cover"></div>');
33006  $('form.menusort div.cover').hide();
33007  $('form.menusort input[type="submit"]').hide();
33008
33009  $('form.menusort').each(function(){
33010    refresh_menu_sort($('ol.dd-list', this).first());
33011  });
33012
33013  $('.dd').nestable({ maxDepth: 5 });
33014  $('.dd').on('change', function() {
33015    var $list = $('ol.dd-list', this).first();
33016    refresh_menu_sort($list);
33017    save_menu_sort($list);
33018  });
33019
33020}
33021
33022function refresh_menu_sort(menu) {
33023
33024  $('select[id$="menu"]', menu).each(function(index){
33025    $(this).val($(this).parents('form.menusort').data('menu'));
33026    $(this).hide();
33027  });
33028
33029  $('select[id$="weight"]', menu).each(function(index){
33030    $(this).val($(this).parent().index()+1);
33031    $(this).hide();
33032  });
33033
33034  $('input[id$="parent_id"], select[id$="parent_id"]', menu).each(function(index){
33035    $parent_id = 0;
33036    if ($(this).parent().parents('li').length > 0) {
33037      $parent_id = $(this).parent().parents('li').data('id');
33038    }
33039    $(this).val($parent_id);
33040    $(this).hide();
33041  });
33042
33043}
33044
33045function save_menu_sort(menu) {
33046  $('div.cover', menu.parents('form')).show();
33047  menu.parents('form').submit();
33048}
33049
33050function start_menulinks_actionsmenu($menulinks) {
33051
33052  $('div.actions', $menulinks).each(function(){
33053    $('ul', $(this)).hide();
33054    $('a.toggle', $(this)).click(function(e){
33055      $('ul', $(this).parents('div.actions')).toggle();
33056      e.preventDefault();
33057    });
33058  });
33059
33060  $(document).click (function (e) {
33061    if ($(e.target).parents('div.actions').length < 1) {
33062      $('form.menusort div.actions ul').hide();
33063    }
33064  });
33065
33066}
33067
33068function start_progress() {
33069
33070  reset_progress();
33071
33072  $('form').submit(function(){
33073
33074    remove_errors($(this));
33075
33076    $('input[type="submit"]', $(this)).attr('disabled', true);
33077    $('input[type="submit"]', $(this)).css('opacity', '0.5');
33078    $('div.progress', $(this)).fadeIn();
33079
33080  });
33081
33082}
33083
33084function start_form_ajax_errors() {
33085
33086  // scoped to <form> submissions only - remote links (the more common
33087  // ajax:remote pattern in this app) aren't covered by this handler
33088
33089  $(document).on('ajax:error', 'form', function(){
33090
33091    reset_progress();
33092
33093    $errors = $('<div class="info errors"></div>');
33094    $errors.append('<p>Sorry, something went wrong submitting this form. Please try again.</p>');
33095    $(this).prepend($errors);
33096    $('html, body').animate({ scrollTop: $errors.offset().top - 20 }, 300);
33097
33098  });
33099
33100}
33101
33102function reset_progress() {
33103
33104  $('div.progress').hide();
33105  $('input[type="submit"]').attr('disabled', false);
33106  $('input[type="submit"]').css('opacity', '1');
33107
33108}
33109
33110function start_delete() {
33111
33112  if ($('div.admin.delete form').length > 0) {
33113    var $form = $('div.admin.delete form');
33114  } else if ($('div.admin.delete_all form').length > 0) {
33115    var $form = $('div.admin.delete_all form');
33116  }
33117
33118  $('input[type="submit"]', $form).attr('disabled', true);
33119  $('input[type="submit"]', $form).css('opacity', '0.5');
33120
33121  $('input#delete_confirm').keyup(function(){
33122
33123    if ($('input#delete_confirm').val() == 'DELETE') {
33124      $('input[type="submit"]', $form).attr('disabled', false);
33125      $('input[type="submit"]', $form).css('opacity', '1');
33126    } else {
33127      $('input[type="submit"]', $form).attr('disabled', true);
33128      $('input[type="submit"]', $form).css('opacity', '0.5');
33129    }
33130
33131  });
33132
33133}
33134
33135function scroll_target($target, $offset=0) {
33136
33137  $('html, body').animate({
33138    scrollTop: $target.offset().top-$offset
33139  }, 500);
33140
33141}
33142
33143function start_flash_scroll() {
33144
33145  var $flash = $('#flash-message');
33146
33147  if ($flash.length === 0 || $flash.children().length === 0) { return; }
33148  if (window.location.hash && window.location.hash.length > 1) { return; }
33149
33150  var flashTop = $flash.offset().top;
33151  var flashBottom = flashTop + $flash.outerHeight();
33152  var viewportTop = $(window).scrollTop();
33153  var viewportBottom = viewportTop + $(window).height();
33154
33155  if (flashTop < viewportTop || flashBottom > viewportBottom) {
33156    scroll_target($flash, 100);
33157  }
33158
33159}
33160
33161function getParameterByName(name, url) {
33162    if (!url) {
33163      url = window.location.href;
33164    }
33165    name = name.replace(/[\[\]]/g, "\\$&");
33166    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
33167        results = regex.exec(url);
33168    if (!results) return null;
33169    if (!results[2]) return '';
33170    return decodeURIComponent(results[2].replace(/\+/g, " "));
33171}
33172
33173function start_mobile() {
33174
33175  /* Scrollable navbar */
33176  menu_width = -5;
33177  $('div#primary ul.menu li:not(.highlight)').each(function(){
33178    menu_width += $(this).width()+5;
33179  });
33180  reset_mobile_navbar(menu_width);
33181  $(window).resize(function(){
33182    reset_mobile_navbar(menu_width);
33183  });
33184
33185  /* Scrollable tables */
33186  $('div#content table:not(.admin):not(.noscroll)').each(function(){
33187    $(this).wrap('<div class="overflow"></div>');
33188  });
33189
33190}
33191
33192function reset_mobile_navbar(menu_width) {
33193
33194  if ($('div#primary').width() < menu_width) {
33195    $('div#primary').addClass('scroll');
33196    reset_mobile_navbar_scroll();
33197    $('div#primary ul.menu').scroll(function(){
33198      reset_mobile_navbar_scroll();
33199    });
33200  } else {
33201    $('div#primary').removeClass('scroll');
33202    $('div#primary').removeClass('scroll-left');
33203    $('div#primary').removeClass('scroll-right');
33204  }
33205
33206}
33207
33208function reset_mobile_navbar_scroll() {
33209
33210  if ($('div#primary ul.menu li:first').position().left < 0) {
33211    $('div#primary').addClass('scroll-left');
33212  } else {
33213    $('div#primary').removeClass('scroll-left');
33214  }
33215  if (($('div#primary ul.menu').width() - Math.floor($('div#primary ul.menu li:not(.highlight):last').position().left) - $('div#primary ul.menu li:not(.highlight):last').width() - 5) < 0) {
33216    $('div#primary').addClass('scroll-right');
33217  } else {
33218    $('div#primary').removeClass('scroll-right');
33219  }
33220
33221}
33222
33223function start_contextual() {
33224
33225  if ($('div#contextual').length > 0) {
33226    reset_contextual();
33227    $(window).resize(function(){
33228      reset_contextual();
33229    });
33230    $(window).scroll(function(){
33231      reset_contextual();
33232    });
33233  }
33234
33235}
33236
33237function reset_contextual() {
33238
33239  $('div#contextual').css('min-height', ($('div#contextual ul').height()+2)+'px');
33240  $('div#contextual ul').css('max-width', ($('div#contextual').width()-8));
33241  if ($('div#contextual')[0].getBoundingClientRect().top < 10) {
33242    $('div#contextual').addClass('fixed');
33243  } else {
33244    $('div#contextual').removeClass('fixed');
33245  }
33246
33247}
33248
33249function reset_export_links() {
33250
33251  $('a.export').each(function(){
33252    href = $(this).attr('href').split('?')[0];
33253    $(this).attr('href', href+window.location.search);
33254  });
33255
33256}
33257
33258function start_tabs() {
33259
33260  $('ul.tabs').each(function(){
33261    if (window.location.hash) {
33262      $('a[href="'+window.location.hash+'"]').addClass('active');
33263    }
33264    if ($('li a.active', $(this)).length == 0) {
33265      $anchor = $('li:first-child a', $(this)).addClass('active');
33266    }
33267    $anchor = $('li a.active:first', $(this));
33268    set_tab_for($anchor);
33269    $('li a', $tabs).each(function(){
33270      $(this).click(function(e){
33271        set_tab_for($(this));
33272        e.preventDefault();
33273      });
33274    });
33275  });
33276
33277  $('div.radio.tabs div.radios').each(function(){
33278    if ($('div.option input[type="radio"]:checked', $(this)).length == 0) {
33279      $('div.option:first-child input[type="radio"]', $(this)).prop('checked', true);
33280    }
33281    $('div.option input[type="radio"]', $(this)).each(function(){
33282      if ($(this).prop('checked')) {
33283        $('div#'+$(this).attr('id')).show();
33284      } else {
33285        $('div#'+$(this).attr('id')).hide();
33286      }
33287    });
33288    $('div.option input[type="radio"]', $(this)).change(function(e){
33289      $('div.option input[type="radio"]', $(this).parents('div.radios')).each(function(){
33290        if ($(this).prop('checked')) {
33291          $('div#'+$(this).attr('id')).show();
33292        } else {
33293          $('div#'+$(this).attr('id')).hide();
33294        }
33295      });
33296    });
33297  });
33298
33299}
33300
33301function set_tab_for($anchor) {
33302
33303  $tabs = $anchor.parents('ul.tabs');
33304  $('li a', $tabs).removeClass('active');
33305  $anchor.addClass('active');
33306  $('li a', $tabs).each(function(){
33307    $('div'+$(this).attr('href')).hide();
33308    if ($(this).hasClass('active')) {
33309      $('div'+$(this).attr('href')).show();
33310    }
33311  });
33312
33313}
33314
33315function start_reveal($checkbox) {
33316
33317  $checkbox.change(function(){
33318    refresh_reveal($(this));
33319  });
33320  refresh_reveal($checkbox);
33321
33322}
33323
33324function refresh_reveal($checkbox) {
33325
33326  if ($checkbox.prop('checked')) {
33327    $('div.content', $checkbox.parents('div.reveal')).show();
33328  } else {
33329    $('div.content', $checkbox.parents('div.reveal')).hide();
33330  }
33331
33332}
33333
33334function start_section_toggle() {
33335
33336  $('section div.label').each(function(){
33337    $('ul.options', $(this)).hide();
33338    $('h2.toggle', $(this)).click(function(e){
33339      $('ul.options', $(this).parents('div.label')).toggle();
33340      e.preventDefault();
33341    });
33342  });
33343
33344  $(document).click (function (e) {
33345    if ($(e.target).parents('div.label').length < 1) {
33346      $('div.label ul.options').hide();
33347    }
33348  });
33349
33350}
33351
33352function start_options() {
33353
33354  $('ul.options').each(function(){
33355    if (window.location.hash) {
33356      $('a[href="'+window.location.hash+'"]').addClass('active');
33357    }
33358    if ($('li a.active', $(this)).length == 0) {
33359      $anchor = $('li:first-child a', $(this)).addClass('active');
33360    }
33361    $anchor = $('li a.active:first', $(this));
33362    $('h2.toggle', $anchor.parents('div.label')).text($anchor.text());
33363    set_option_for($anchor);
33364    $('li a', $options).each(function(){
33365      $(this).click(function(e){
33366        set_option_for($(this));
33367        $('div.label ul.options').hide();
33368        $('h2.toggle', $(this).parents('div.label')).text($(this).text());
33369        e.preventDefault();
33370      });
33371    });
33372  });
33373
33374}
33375
33376function set_option_for($anchor) {
33377
33378  $options = $anchor.parents('ul.options');
33379  $('li a', $options).removeClass('active');
33380  $anchor.addClass('active');
33381  $('li a', $options).each(function(){
33382    $('div'+$(this).attr('href')).hide();
33383    if ($(this).hasClass('active')) {
33384      $('div'+$(this).attr('href')).show();
33385    }
33386  });
33387
33388}
33389
33390function start_reveal_select($select) {
33391
33392  $select.change(function(){
33393    refresh_reveal_select($(this));
33394  });
33395  refresh_reveal_select($select);
33396
33397}
33398
33399function refresh_reveal_select($select) {
33400
33401  $('option', $select).each(function(){
33402    $('#'+$(this).attr('class')).hide();
33403  });
33404  $('#'+$('option:selected', $select).attr('class')).show();
33405
33406}
33407
33408function start_toggle(menu) {
33409
33410  $('.toggle', menu).click(function(e){
33411    $('li.parent', menu).not($(this).parents('li.parent')).removeClass('expanded');
33412    $(this).parents('li.parent').toggleClass('expanded');
33413    e.preventDefault();
33414  });
33415
33416  $('li.parent ul li a').click(function(e){
33417    $(this).parents('li.parent').removeClass('expanded');
33418  });
33419
33420  $(document).click (function (e) {
33421    if ($(e.target).parents(menu).length < 1) {
33422      $('li.parent', menu).removeClass('expanded');
33423    }
33424  });
33425
33426}
33427
33428function start_accordion(accordion) {
33429
33430  $('.toggle', accordion).click(function(e){
33431    $(this).parents('div.panel').toggleClass('expanded');
33432    e.preventDefault();
33433  });
33434
33435}
33436
33437function start_date_notice($input) {
33438
33439  console.log('Start date notice');
33440  $input.change(function(){
33441    refresh_date_notice($input);
33442  });
33443  refresh_date_notice($input);
33444
33445}
33446
33447function refresh_date_notice($input) {
33448
33449  if ($input.val() != '') {
33450    $date = new Date($input.val());
33451    $now = new Date();
33452    $now.setHours(0,0,0,0);
33453    if ($date < $now) {
33454      $('div.notice', $input.parents('div.field')).show();
33455    } else {
33456      $('div.notice', $input.parents('div.field')).hide();
33457    }
33458  } else {
33459    $('div.notice', $input.parents('div.field')).hide();
33460  }
33461
33462}
33463
33464function start_trial() {
33465
33466  var $trial = getParameterByName('trial');
33467  if ($trial) {
33468    $modal = '';
33469    $modal += '<div class="large" role="dialog" aria-labelledby="Welcome to your membership website">';
33470    $modal += '<button class="close mdi mdi-close" aria-label="Close" />';
33471    $modal += '<div class="textcenter xxxlarge grey mdi mdi-check-circle"></div>';
33472    $modal += '<h1 class="textcenter">Welcome to your<br />membership website</h1>';
33473    $modal += '<hr />';
33474    $modal += '<p class="textcenter">Use the toolbar at the top of the screen<br />to explore the software.</p>';
33475    $modal += '<p class="textcenter"><a href="/users/checklist" class="button">Get started</a></p>';
33476    $modal += '</div>';
33477    start_modal($modal);
33478  }
33479
33480}
33481
33482$(document).bind('ready', function() {
33483
33484  console.log('Start ready scripts.');
33485
33486  start_mobile();
33487  start_contextual();
33488  start_forms();
33489  start_tabs();
33490  start_options();
33491  start_trial();
33492  reset_export_links();
33493  show_cookie_notice();
33494  start_show_password();
33495  start_form_ajax_errors();
33496  start_flash_scroll();
33497
33498});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.