/*!
 * ====================================================
 * kityminder - v1.3.5 - 2015-05-15
 * https://github.com/fex-team/kityminder
 * GitHub: https://github.com/fex-team/kityminder.git 
 * Copyright (c) 2015 f-cube @ FEX; Licensed MIT
 * ====================================================
 */

(function(window) {

/*!
 * jQuery JavaScript Library v2.1.1
 * http://jquery.com/
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 *
 * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-05-01T17:11Z
 */

(function( global, factory ) {

	if ( typeof module === "object" && typeof module.exports === "object" ) {
		// For CommonJS and CommonJS-like environments where a proper window is present,
		// execute the factory and get jQuery
		// For environments that do not inherently posses a window with a document
		// (such as Node.js), expose a jQuery-making factory as module.exports
		// This accentuates the need for the creation of a real window
		// e.g. var jQuery = require("jquery")(window);
		// See ticket #14549 for more info
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	} else {
		factory( global );
	}

// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

// Can't do this because several apps including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
// Support: Firefox 18+
//

var arr = [];

var slice = arr.slice;

var concat = arr.concat;

var push = arr.push;

var indexOf = arr.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var support = {};



var
	// Use the correct document accordingly with window argument (sandbox)
	document = window.document,

	version = "2.1.1",

	// Define a local copy of jQuery
	jQuery = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	},

	// Support: Android<4.1
	// Make sure we trim BOM and NBSP
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

	// Matches dashed string for camelizing
	rmsPrefix = /^-ms-/,
	rdashAlpha = /-([\da-z])/gi,

	// Used by jQuery.camelCase as callback to replace()
	fcamelCase = function( all, letter ) {
		return letter.toUpperCase();
	};

jQuery.fn = jQuery.prototype = {
	// The current version of jQuery being used
	jquery: version,

	constructor: jQuery,

	// Start with an empty selector
	selector: "",

	// The default length of a jQuery object is 0
	length: 0,

	toArray: function() {
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num != null ?

			// Return just the one element from the set
			( num < 0 ? this[ num + this.length ] : this[ num ] ) :

			// Return all the elements in a clean array
			slice.call( this );
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;
		ret.context = this.context;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	// (You can seed the arguments with an array of args, but this is
	// only used internally.)
	each: function( callback, args ) {
		return jQuery.each( this, callback, args );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function( elem, i ) {
			return callback.call( elem, i, elem );
		}));
	},

	slice: function() {
		return this.pushStack( slice.apply( this, arguments ) );
	},

	first: function() {
		return this.eq( 0 );
	},

	last: function() {
		return this.eq( -1 );
	},

	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
	},

	end: function() {
		return this.prevObject || this.constructor(null);
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: push,
	sort: arr.sort,
	splice: arr.splice
};

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null ) {
			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend({
	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},

	noop: function() {},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return jQuery.type(obj) === "function";
	},

	isArray: Array.isArray,

	isWindow: function( obj ) {
		return obj != null && obj === obj.window;
	},

	isNumeric: function( obj ) {
		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
		// subtraction forces infinities to NaN
		return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0;
	},

	isPlainObject: function( obj ) {
		// Not plain objects:
		// - Any object or value whose internal [[Class]] property is not "[object Object]"
		// - DOM nodes
		// - window
		if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
			return false;
		}

		if ( obj.constructor &&
				!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
			return false;
		}

		// If the function hasn't returned already, we're confident that
		// |obj| is a plain object, created by {} or constructed with new Object
		return true;
	},

	isEmptyObject: function( obj ) {
		var name;
		for ( name in obj ) {
			return false;
		}
		return true;
	},

	type: function( obj ) {
		if ( obj == null ) {
			return obj + "";
		}
		// Support: Android < 4.0, iOS < 6 (functionish RegExp)
		return typeof obj === "object" || typeof obj === "function" ?
			class2type[ toString.call(obj) ] || "object" :
			typeof obj;
	},

	// Evaluates a script in a global context
	globalEval: function( code ) {
		var script,
			indirect = eval;

		code = jQuery.trim( code );

		if ( code ) {
			// If the code includes a valid, prologue position
			// strict mode pragma, execute code by injecting a
			// script tag into the document.
			if ( code.indexOf("use strict") === 1 ) {
				script = document.createElement("script");
				script.text = code;
				document.head.appendChild( script ).parentNode.removeChild( script );
			} else {
			// Otherwise, avoid the DOM node creation, insertion
			// and removal by using an indirect global eval
				indirect( code );
			}
		}
	},

	// Convert dashed to camelCase; used by the css and data modules
	// Microsoft forgot to hump their vendor prefix (#9572)
	camelCase: function( string ) {
		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
	},

	// args is for internal usage only
	each: function( obj, callback, args ) {
		var value,
			i = 0,
			length = obj.length,
			isArray = isArraylike( obj );

		if ( args ) {
			if ( isArray ) {
				for ( ; i < length; i++ ) {
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
						break;
					}
				}
			} else {
				for ( i in obj ) {
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
						break;
					}
				}
			}

		// A special, fast, case for the most common use of each
		} else {
			if ( isArray ) {
				for ( ; i < length; i++ ) {
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
						break;
					}
				}
			} else {
				for ( i in obj ) {
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
						break;
					}
				}
			}
		}

		return obj;
	},

	// Support: Android<4.1
	trim: function( text ) {
		return text == null ?
			"" :
			( text + "" ).replace( rtrim, "" );
	},

	// results is for internal usage only
	makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArraylike( Object(arr) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
					[ arr ] : arr
				);
			} else {
				push.call( ret, arr );
			}
		}

		return ret;
	},

	inArray: function( elem, arr, i ) {
		return arr == null ? -1 : indexOf.call( arr, elem, i );
	},

	merge: function( first, second ) {
		var len = +second.length,
			j = 0,
			i = first.length;

		for ( ; j < len; j++ ) {
			first[ i++ ] = second[ j ];
		}

		first.length = i;

		return first;
	},

	grep: function( elems, callback, invert ) {
		var callbackInverse,
			matches = [],
			i = 0,
			length = elems.length,
			callbackExpect = !invert;

		// Go through the array, only saving the items
		// that pass the validator function
		for ( ; i < length; i++ ) {
			callbackInverse = !callback( elems[ i ], i );
			if ( callbackInverse !== callbackExpect ) {
				matches.push( elems[ i ] );
			}
		}

		return matches;
	},

	// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var value,
			i = 0,
			length = elems.length,
			isArray = isArraylike( elems ),
			ret = [];

		// Go through the array, translating each of the items to their new values
		if ( isArray ) {
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}

		// Go through every key on the object,
		} else {
			for ( i in elems ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}
		}

		// Flatten any nested arrays
		return concat.apply( [], ret );
	},

	// A global GUID counter for objects
	guid: 1,

	// Bind a function to a context, optionally partially applying any
	// arguments.
	proxy: function( fn, context ) {
		var tmp, args, proxy;

		if ( typeof context === "string" ) {
			tmp = fn[ context ];
			context = fn;
			fn = tmp;
		}

		// Quick check to determine if target is callable, in the spec
		// this throws a TypeError, but we will just return undefined.
		if ( !jQuery.isFunction( fn ) ) {
			return undefined;
		}

		// Simulated bind
		args = slice.call( arguments, 2 );
		proxy = function() {
			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
		};

		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || jQuery.guid++;

		return proxy;
	},

	now: Date.now,

	// jQuery.support is not used in Core but other projects attach their
	// properties to it so it needs to exist.
	support: support
});

// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

function isArraylike( obj ) {
	var length = obj.length,
		type = jQuery.type( obj );

	if ( type === "function" || jQuery.isWindow( obj ) ) {
		return false;
	}

	if ( obj.nodeType === 1 && length ) {
		return true;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
var Sizzle =
/*!
 * Sizzle CSS Selector Engine v1.10.19
 * http://sizzlejs.com/
 *
 * Copyright 2013 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-04-18
 */
(function( window ) {

var i,
	support,
	Expr,
	getText,
	isXML,
	tokenize,
	compile,
	select,
	outermostContext,
	sortInput,
	hasDuplicate,

	// Local document vars
	setDocument,
	document,
	docElem,
	documentIsHTML,
	rbuggyQSA,
	rbuggyMatches,
	matches,
	contains,

	// Instance-specific data
	expando = "sizzle" + -(new Date()),
	preferredDoc = window.document,
	dirruns = 0,
	done = 0,
	classCache = createCache(),
	tokenCache = createCache(),
	compilerCache = createCache(),
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
		}
		return 0;
	},

	// General-purpose constants
	strundefined = typeof undefined,
	MAX_NEGATIVE = 1 << 31,

	// Instance methods
	hasOwn = ({}).hasOwnProperty,
	arr = [],
	pop = arr.pop,
	push_native = arr.push,
	push = arr.push,
	slice = arr.slice,
	// Use a stripped-down indexOf if we can't use a native one
	indexOf = arr.indexOf || function( elem ) {
		var i = 0,
			len = this.length;
		for ( ; i < len; i++ ) {
			if ( this[i] === elem ) {
				return i;
			}
		}
		return -1;
	},

	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",

	// Regular expressions

	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
	whitespace = "[\\x20\\t\\r\\n\\f]",
	// http://www.w3.org/TR/css3-syntax/#characters
	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",

	// Loosely modeled on CSS identifier characters
	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
	identifier = characterEncoding.replace( "w", "w#" ),

	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
		// Operator (capture 2)
		"*([*^$|!~]?=)" + whitespace +
		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
		"*\\]",

	pseudos = ":(" + characterEncoding + ")(?:\\((" +
		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
		// 1. quoted (capture 3; capture 4 or capture 5)
		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
		// 2. simple (capture 6)
		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
		// 3. anything else (capture 2)
		".*" +
		")\\)|)",

	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),

	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),

	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),

	rpseudo = new RegExp( pseudos ),
	ridentifier = new RegExp( "^" + identifier + "$" ),

	matchExpr = {
		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
		"ATTR": new RegExp( "^" + attributes ),
		"PSEUDO": new RegExp( "^" + pseudos ),
		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
		// For use in libraries implementing .is()
		// We use this for POS matching in `select`
		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
	},

	rinputs = /^(?:input|select|textarea|button)$/i,
	rheader = /^h\d$/i,

	rnative = /^[^{]+\{\s*\[native \w/,

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

	rsibling = /[+~]/,
	rescape = /'|\\/g,

	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
	funescape = function( _, escaped, escapedWhitespace ) {
		var high = "0x" + escaped - 0x10000;
		// NaN means non-codepoint
		// Support: Firefox<24
		// Workaround erroneous numeric interpretation of +"0x"
		return high !== high || escapedWhitespace ?
			escaped :
			high < 0 ?
				// BMP codepoint
				String.fromCharCode( high + 0x10000 ) :
				// Supplemental Plane codepoint (surrogate pair)
				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
	};

// Optimize for push.apply( _, NodeList )
try {
	push.apply(
		(arr = slice.call( preferredDoc.childNodes )),
		preferredDoc.childNodes
	);
	// Support: Android<4.0
	// Detect silently failing push.apply
	arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
	push = { apply: arr.length ?

		// Leverage slice if possible
		function( target, els ) {
			push_native.apply( target, slice.call(els) );
		} :

		// Support: IE<9
		// Otherwise append directly
		function( target, els ) {
			var j = target.length,
				i = 0;
			// Can't trust NodeList.length
			while ( (target[j++] = els[i++]) ) {}
			target.length = j - 1;
		}
	};
}

function Sizzle( selector, context, results, seed ) {
	var match, elem, m, nodeType,
		// QSA vars
		i, groups, old, nid, newContext, newSelector;

	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
		setDocument( context );
	}

	context = context || document;
	results = results || [];

	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
		return [];
	}

	if ( documentIsHTML && !seed ) {

		// Shortcuts
		if ( (match = rquickExpr.exec( selector )) ) {
			// Speed-up: Sizzle("#ID")
			if ( (m = match[1]) ) {
				if ( nodeType === 9 ) {
					elem = context.getElementById( m );
					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document (jQuery #6963)
					if ( elem && elem.parentNode ) {
						// Handle the case where IE, Opera, and Webkit return items
						// by name instead of ID
						if ( elem.id === m ) {
							results.push( elem );
							return results;
						}
					} else {
						return results;
					}
				} else {
					// Context is not a document
					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
						contains( context, elem ) && elem.id === m ) {
						results.push( elem );
						return results;
					}
				}

			// Speed-up: Sizzle("TAG")
			} else if ( match[2] ) {
				push.apply( results, context.getElementsByTagName( selector ) );
				return results;

			// Speed-up: Sizzle(".CLASS")
			} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
				push.apply( results, context.getElementsByClassName( m ) );
				return results;
			}
		}

		// QSA path
		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
			nid = old = expando;
			newContext = context;
			newSelector = nodeType === 9 && selector;

			// qSA works strangely on Element-rooted queries
			// We can work around this by specifying an extra ID on the root
			// and working up from there (Thanks to Andrew Dupont for the technique)
			// IE 8 doesn't work on object elements
			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
				groups = tokenize( selector );

				if ( (old = context.getAttribute("id")) ) {
					nid = old.replace( rescape, "\\$&" );
				} else {
					context.setAttribute( "id", nid );
				}
				nid = "[id='" + nid + "'] ";

				i = groups.length;
				while ( i-- ) {
					groups[i] = nid + toSelector( groups[i] );
				}
				newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
				newSelector = groups.join(",");
			}

			if ( newSelector ) {
				try {
					push.apply( results,
						newContext.querySelectorAll( newSelector )
					);
					return results;
				} catch(qsaError) {
				} finally {
					if ( !old ) {
						context.removeAttribute("id");
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

/**
 * Create key-value caches of limited size
 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
function createCache() {
	var keys = [];

	function cache( key, value ) {
		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
		if ( keys.push( key + " " ) > Expr.cacheLength ) {
			// Only keep the most recent entries
			delete cache[ keys.shift() ];
		}
		return (cache[ key + " " ] = value);
	}
	return cache;
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
	fn[ expando ] = true;
	return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created div and expects a boolean result
 */
function assert( fn ) {
	var div = document.createElement("div");

	try {
		return !!fn( div );
	} catch (e) {
		return false;
	} finally {
		// Remove from its parent by default
		if ( div.parentNode ) {
			div.parentNode.removeChild( div );
		}
		// release memory in IE
		div = null;
	}
}

/**
 * Adds the same handler for all of the specified attrs
 * @param {String} attrs Pipe-separated list of attributes
 * @param {Function} handler The method that will be applied
 */
function addHandle( attrs, handler ) {
	var arr = attrs.split("|"),
		i = attrs.length;

	while ( i-- ) {
		Expr.attrHandle[ arr[i] ] = handler;
	}
}

/**
 * Checks document order of two siblings
 * @param {Element} a
 * @param {Element} b
 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
 */
function siblingCheck( a, b ) {
	var cur = b && a,
		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
			( ~b.sourceIndex || MAX_NEGATIVE ) -
			( ~a.sourceIndex || MAX_NEGATIVE );

	// Use IE sourceIndex if available on both nodes
	if ( diff ) {
		return diff;
	}

	// Check if b follows a
	if ( cur ) {
		while ( (cur = cur.nextSibling) ) {
			if ( cur === b ) {
				return -1;
			}
		}
	}

	return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return name === "input" && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return (name === "input" || name === "button") && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
	return markFunction(function( argument ) {
		argument = +argument;
		return markFunction(function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ (j = matchIndexes[i]) ] ) {
					seed[j] = !(matches[j] = seed[j]);
				}
			}
		});
	});
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
	return context && typeof context.getElementsByTagName !== strundefined && context;
}

// Expose support vars for convenience
support = Sizzle.support = {};

/**
 * Detects XML nodes
 * @param {Element|Object} elem An element or a document
 * @returns {Boolean} True iff elem is a non-HTML XML node
 */
isXML = Sizzle.isXML = function( elem ) {
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833)
	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function( node ) {
	var hasCompare,
		doc = node ? node.ownerDocument || node : preferredDoc,
		parent = doc.defaultView;

	// If no document and documentElement is available, return
	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
		return document;
	}

	// Set our document
	document = doc;
	docElem = doc.documentElement;

	// Support tests
	documentIsHTML = !isXML( doc );

	// Support: IE>8
	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
	// IE6-8 do not support the defaultView property so parent will be undefined
	if ( parent && parent !== parent.top ) {
		// IE11 does not have attachEvent, so all must suffer
		if ( parent.addEventListener ) {
			parent.addEventListener( "unload", function() {
				setDocument();
			}, false );
		} else if ( parent.attachEvent ) {
			parent.attachEvent( "onunload", function() {
				setDocument();
			});
		}
	}

	/* Attributes
	---------------------------------------------------------------------- */

	// Support: IE<8
	// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
	support.attributes = assert(function( div ) {
		div.className = "i";
		return !div.getAttribute("className");
	});

	/* getElement(s)By*
	---------------------------------------------------------------------- */

	// Check if getElementsByTagName("*") returns only elements
	support.getElementsByTagName = assert(function( div ) {
		div.appendChild( doc.createComment("") );
		return !div.getElementsByTagName("*").length;
	});

	// Check if getElementsByClassName can be trusted
	support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {
		div.innerHTML = "<div class='a'></div><div class='a i'></div>";

		// Support: Safari<4
		// Catch class over-caching
		div.firstChild.className = "i";
		// Support: Opera<10
		// Catch gEBCN failure to find non-leading classes
		return div.getElementsByClassName("i").length === 2;
	});

	// Support: IE<10
	// Check if getElementById returns elements by name
	// The broken getElementById methods don't pick up programatically-set names,
	// so use a roundabout getElementsByName test
	support.getById = assert(function( div ) {
		docElem.appendChild( div ).id = expando;
		return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
	});

	// ID find and filter
	if ( support.getById ) {
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
				var m = context.getElementById( id );
				// Check parentNode to catch when Blackberry 4.6 returns
				// nodes that are no longer in the document #6963
				return m && m.parentNode ? [ m ] : [];
			}
		};
		Expr.filter["ID"] = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute("id") === attrId;
			};
		};
	} else {
		// Support: IE6/7
		// getElementById is not reliable as a find shortcut
		delete Expr.find["ID"];

		Expr.filter["ID"] =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
				return node && node.value === attrId;
			};
		};
	}

	// Tag
	Expr.find["TAG"] = support.getElementsByTagName ?
		function( tag, context ) {
			if ( typeof context.getElementsByTagName !== strundefined ) {
				return context.getElementsByTagName( tag );
			}
		} :
		function( tag, context ) {
			var elem,
				tmp = [],
				i = 0,
				results = context.getElementsByTagName( tag );

			// Filter out possible comments
			if ( tag === "*" ) {
				while ( (elem = results[i++]) ) {
					if ( elem.nodeType === 1 ) {
						tmp.push( elem );
					}
				}

				return tmp;
			}
			return results;
		};

	// Class
	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
		if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
			return context.getElementsByClassName( className );
		}
	};

	/* QSA/matchesSelector
	---------------------------------------------------------------------- */

	// QSA and matchesSelector support

	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
	rbuggyMatches = [];

	// qSa(:focus) reports false when true (Chrome 21)
	// We allow this because of a bug in IE8/9 that throws an error
	// whenever `document.activeElement` is accessed on an iframe
	// So, we allow :focus to pass through QSA all the time to avoid the IE error
	// See http://bugs.jquery.com/ticket/13378
	rbuggyQSA = [];

	if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
		// Build QSA regex
		// Regex strategy adopted from Diego Perini
		assert(function( div ) {
			// Select is set to empty string on purpose
			// This is to test IE's treatment of not explicitly
			// setting a boolean content attribute,
			// since its presence should be enough
			// http://bugs.jquery.com/ticket/12359
			div.innerHTML = "<select msallowclip=''><option selected=''></option></select>";

			// Support: IE8, Opera 11-12.16
			// Nothing should be selected when empty strings follow ^= or $= or *=
			// The test attribute must be unknown in Opera but "safe" for WinRT
			// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
			if ( div.querySelectorAll("[msallowclip^='']").length ) {
				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
			}

			// Support: IE8
			// Boolean attributes and "value" are not treated correctly
			if ( !div.querySelectorAll("[selected]").length ) {
				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
			}

			// Webkit/Opera - :checked should return selected option elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":checked").length ) {
				rbuggyQSA.push(":checked");
			}
		});

		assert(function( div ) {
			// Support: Windows 8 Native Apps
			// The type and name attributes are restricted during .innerHTML assignment
			var input = doc.createElement("input");
			input.setAttribute( "type", "hidden" );
			div.appendChild( input ).setAttribute( "name", "D" );

			// Support: IE8
			// Enforce case-sensitivity of name attribute
			if ( div.querySelectorAll("[name=d]").length ) {
				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
			}

			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":enabled").length ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Opera 10-11 does not throw on post-comma invalid pseudos
			div.querySelectorAll("*,:x");
			rbuggyQSA.push(",.*:");
		});
	}

	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
		docElem.webkitMatchesSelector ||
		docElem.mozMatchesSelector ||
		docElem.oMatchesSelector ||
		docElem.msMatchesSelector) )) ) {

		assert(function( div ) {
			// Check to see if it's possible to do matchesSelector
			// on a disconnected node (IE 9)
			support.disconnectedMatch = matches.call( div, "div" );

			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( div, "[s!='']:x" );
			rbuggyMatches.push( "!=", pseudos );
		});
	}

	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );

	/* Contains
	---------------------------------------------------------------------- */
	hasCompare = rnative.test( docElem.compareDocumentPosition );

	// Element contains another
	// Purposefully does not implement inclusive descendent
	// As in, an element does not contain itself
	contains = hasCompare || rnative.test( docElem.contains ) ?
		function( a, b ) {
			var adown = a.nodeType === 9 ? a.documentElement : a,
				bup = b && b.parentNode;
			return a === bup || !!( bup && bup.nodeType === 1 && (
				adown.contains ?
					adown.contains( bup ) :
					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
			));
		} :
		function( a, b ) {
			if ( b ) {
				while ( (b = b.parentNode) ) {
					if ( b === a ) {
						return true;
					}
				}
			}
			return false;
		};

	/* Sorting
	---------------------------------------------------------------------- */

	// Document order sorting
	sortOrder = hasCompare ?
	function( a, b ) {

		// Flag for duplicate removal
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		// Sort on method existence if only one input has compareDocumentPosition
		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
		if ( compare ) {
			return compare;
		}

		// Calculate position if both inputs belong to the same document
		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
			a.compareDocumentPosition( b ) :

			// Otherwise we know they are disconnected
			1;

		// Disconnected nodes
		if ( compare & 1 ||
			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {

			// Choose the first element that is related to our preferred document
			if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
				return -1;
			}
			if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
				return 1;
			}

			// Maintain original order
			return sortInput ?
				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
				0;
		}

		return compare & 4 ? -1 : 1;
	} :
	function( a, b ) {
		// Exit early if the nodes are identical
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		var cur,
			i = 0,
			aup = a.parentNode,
			bup = b.parentNode,
			ap = [ a ],
			bp = [ b ];

		// Parentless nodes are either documents or disconnected
		if ( !aup || !bup ) {
			return a === doc ? -1 :
				b === doc ? 1 :
				aup ? -1 :
				bup ? 1 :
				sortInput ?
				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
				0;

		// If the nodes are siblings, we can do a quick check
		} else if ( aup === bup ) {
			return siblingCheck( a, b );
		}

		// Otherwise we need full lists of their ancestors for comparison
		cur = a;
		while ( (cur = cur.parentNode) ) {
			ap.unshift( cur );
		}
		cur = b;
		while ( (cur = cur.parentNode) ) {
			bp.unshift( cur );
		}

		// Walk down the tree looking for a discrepancy
		while ( ap[i] === bp[i] ) {
			i++;
		}

		return i ?
			// Do a sibling check if the nodes have a common ancestor
			siblingCheck( ap[i], bp[i] ) :

			// Otherwise nodes in our document sort first
			ap[i] === preferredDoc ? -1 :
			bp[i] === preferredDoc ? 1 :
			0;
	};

	return doc;
};

Sizzle.matches = function( expr, elements ) {
	return Sizzle( expr, null, null, elements );
};

Sizzle.matchesSelector = function( elem, expr ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	// Make sure that attribute selectors are quoted
	expr = expr.replace( rattributeQuotes, "='$1']" );

	if ( support.matchesSelector && documentIsHTML &&
		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {

		try {
			var ret = matches.call( elem, expr );

			// IE 9's matchesSelector returns false on disconnected nodes
			if ( ret || support.disconnectedMatch ||
					// As well, disconnected nodes are said to be in a document
					// fragment in IE 9
					elem.document && elem.document.nodeType !== 11 ) {
				return ret;
			}
		} catch(e) {}
	}

	return Sizzle( expr, document, null, [ elem ] ).length > 0;
};

Sizzle.contains = function( context, elem ) {
	// Set document vars if needed
	if ( ( context.ownerDocument || context ) !== document ) {
		setDocument( context );
	}
	return contains( context, elem );
};

Sizzle.attr = function( elem, name ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	var fn = Expr.attrHandle[ name.toLowerCase() ],
		// Don't get fooled by Object.prototype properties (jQuery #13807)
		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
			fn( elem, name, !documentIsHTML ) :
			undefined;

	return val !== undefined ?
		val :
		support.attributes || !documentIsHTML ?
			elem.getAttribute( name ) :
			(val = elem.getAttributeNode(name)) && val.specified ?
				val.value :
				null;
};

Sizzle.error = function( msg ) {
	throw new Error( "Syntax error, unrecognized expression: " + msg );
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function( results ) {
	var elem,
		duplicates = [],
		j = 0,
		i = 0;

	// Unless we *know* we can detect duplicates, assume their presence
	hasDuplicate = !support.detectDuplicates;
	sortInput = !support.sortStable && results.slice( 0 );
	results.sort( sortOrder );

	if ( hasDuplicate ) {
		while ( (elem = results[i++]) ) {
			if ( elem === results[ i ] ) {
				j = duplicates.push( i );
			}
		}
		while ( j-- ) {
			results.splice( duplicates[ j ], 1 );
		}
	}

	// Clear input after sorting to release objects
	// See https://github.com/jquery/sizzle/pull/225
	sortInput = null;

	return results;
};

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;

	if ( !nodeType ) {
		// If no nodeType, this is expected to be an array
		while ( (node = elem[i++]) ) {
			// Do not traverse comment nodes
			ret += getText( node );
		}
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
		// Use textContent for elements
		// innerText usage removed for consistency of new lines (jQuery #11153)
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent;
		} else {
			// Traverse its children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem );
			}
		}
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue;
	}
	// Do not include comment or processing instruction nodes

	return ret;
};

Expr = Sizzle.selectors = {

	// Can be adjusted by the user
	cacheLength: 50,

	createPseudo: markFunction,

	match: matchExpr,

	attrHandle: {},

	find: {},

	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},

	preFilter: {
		"ATTR": function( match ) {
			match[1] = match[1].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );

			if ( match[2] === "~=" ) {
				match[3] = " " + match[3] + " ";
			}

			return match.slice( 0, 4 );
		},

		"CHILD": function( match ) {
			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
			match[1] = match[1].toLowerCase();

			if ( match[1].slice( 0, 3 ) === "nth" ) {
				// nth-* requires argument
				if ( !match[3] ) {
					Sizzle.error( match[0] );
				}

				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );

			// other types prohibit arguments
			} else if ( match[3] ) {
				Sizzle.error( match[0] );
			}

			return match;
		},

		"PSEUDO": function( match ) {
			var excess,
				unquoted = !match[6] && match[2];

			if ( matchExpr["CHILD"].test( match[0] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[3] ) {
				match[2] = match[4] || match[5] || "";

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&
				// Get excess from tokenize (recursively)
				(excess = tokenize( unquoted, true )) &&
				// advance to the next closing parenthesis
				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {

				// excess is a negative index
				match[0] = match[0].slice( 0, excess );
				match[2] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		"TAG": function( nodeNameSelector ) {
			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
			return nodeNameSelector === "*" ?
				function() { return true; } :
				function( elem ) {
					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
				};
		},

		"CLASS": function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
				classCache( className, function( elem ) {
					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
				});
		},

		"ATTR": function( name, operator, check ) {
			return function( elem ) {
				var result = Sizzle.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				return operator === "=" ? result === check :
					operator === "!=" ? result !== check :
					operator === "^=" ? check && result.indexOf( check ) === 0 :
					operator === "*=" ? check && result.indexOf( check ) > -1 :
					operator === "$=" ? check && result.slice( -check.length ) === check :
					operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
					false;
			};
		},

		"CHILD": function( type, what, argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, context, xml ) {
					var cache, outerCache, node, diff, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( (node = node[ dir ]) ) {
									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
										return false;
									}
								}
								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {
							// Seek `elem` from a previously-cached index
							outerCache = parent[ expando ] || (parent[ expando ] = {});
							cache = outerCache[ type ] || [];
							nodeIndex = cache[0] === dirruns && cache[1];
							diff = cache[0] === dirruns && cache[2];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( (node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								(diff = nodeIndex = 0) || start.pop()) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						// Use previously-cached element index if available
						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
							diff = cache[1];

						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
						} else {
							// Use the same loop as above to seek `elem` from the start
							while ( (node = ++nodeIndex && node && node[ dir ] ||
								(diff = nodeIndex = 0) || start.pop()) ) {

								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
									// Cache the index of each encountered element
									if ( useCache ) {
										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
									}

									if ( node === elem ) {
										break;
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		"PSEUDO": function( pseudo, argument ) {
			// pseudo-class names are case-insensitive
			// http://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					Sizzle.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as Sizzle does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction(function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf.call( seed, matched[i] );
							seed[ idx ] = !( matches[ idx ] = matched[i] );
						}
					}) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {
		// Potentially complex pseudos
		"not": markFunction(function( selector ) {
			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrim, "$1" ) );

			return matcher[ expando ] ?
				markFunction(function( seed, matches, context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( (elem = unmatched[i]) ) {
							seed[i] = !(matches[i] = elem);
						}
					}
				}) :
				function( elem, context, xml ) {
					input[0] = elem;
					matcher( input, null, xml, results );
					return !results.pop();
				};
		}),

		"has": markFunction(function( selector ) {
			return function( elem ) {
				return Sizzle( selector, elem ).length > 0;
			};
		}),

		"contains": markFunction(function( text ) {
			return function( elem ) {
				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
			};
		}),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// http://www.w3.org/TR/selectors/#lang-pseudo
		"lang": markFunction( function( lang ) {
			// lang value must be a valid identifier
			if ( !ridentifier.test(lang || "") ) {
				Sizzle.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( (elemLang = documentIsHTML ?
						elem.lang :
						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
				return false;
			};
		}),

		// Miscellaneous
		"target": function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		"root": function( elem ) {
			return elem === docElem;
		},

		"focus": function( elem ) {
			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
		},

		// Boolean properties
		"enabled": function( elem ) {
			return elem.disabled === false;
		},

		"disabled": function( elem ) {
			return elem.disabled === true;
		},

		"checked": function( elem ) {
			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
		},

		"selected": function( elem ) {
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {
			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos["empty"]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&

				// Support: IE<8
				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		"first": createPositionalPseudo(function() {
			return [ 0 ];
		}),

		"last": createPositionalPseudo(function( matchIndexes, length ) {
			return [ length - 1 ];
		}),

		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		}),

		"even": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"odd": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		})
	}
};

Expr.pseudos["nth"] = Expr.pseudos["eq"];

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + " " ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || (match = rcomma.exec( soFar )) ) {
			if ( match ) {
				// Don't consume trailing commas as valid
				soFar = soFar.slice( match[0].length ) || soFar;
			}
			groups.push( (tokens = []) );
		}

		matched = false;

		// Combinators
		if ( (match = rcombinators.exec( soFar )) ) {
			matched = match.shift();
			tokens.push({
				value: matched,
				// Cast descendant combinators to space
				type: match[0].replace( rtrim, " " )
			});
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
				(match = preFilters[ type ]( match ))) ) {
				matched = match.shift();
				tokens.push({
					value: matched,
					type: type,
					matches: match
				});
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we're just parsing
	// Otherwise, throw an error or return tokens
	return parseOnly ?
		soFar.length :
		soFar ?
			Sizzle.error( selector ) :
			// Cache the tokens
			tokenCache( selector, groups ).slice( 0 );
};

function toSelector( tokens ) {
	var i = 0,
		len = tokens.length,
		selector = "";
	for ( ; i < len; i++ ) {
		selector += tokens[i].value;
	}
	return selector;
}

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		checkNonElements = base && dir === "parentNode",
		doneName = done++;

	return combinator.first ?
		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, outerCache,
				newCache = [ dirruns, doneName ];

			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
			if ( xml ) {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || (elem[ expando ] = {});
						if ( (oldCache = outerCache[ dir ]) &&
							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

							// Assign to newCache so results back-propagate to previous elements
							return (newCache[ 2 ] = oldCache[ 2 ]);
						} else {
							// Reuse newcache so results back-propagate to previous elements
							outerCache[ dir ] = newCache;

							// A match means we're done; a fail means we have to keep checking
							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
								return true;
							}
						}
					}
				}
			}
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[i]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[0];
}

function multipleContexts( selector, contexts, results ) {
	var i = 0,
		len = contexts.length;
	for ( ; i < len; i++ ) {
		Sizzle( selector, contexts[i], results );
	}
	return results;
}

function condense( unmatched, map, filter, context, xml ) {
	var elem,
		newUnmatched = [],
		i = 0,
		len = unmatched.length,
		mapped = map != null;

	for ( ; i < len; i++ ) {
		if ( (elem = unmatched[i]) ) {
			if ( !filter || filter( elem, context, xml ) ) {
				newUnmatched.push( elem );
				if ( mapped ) {
					map.push( i );
				}
			}
		}
	}

	return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
	if ( postFilter && !postFilter[ expando ] ) {
		postFilter = setMatcher( postFilter );
	}
	if ( postFinder && !postFinder[ expando ] ) {
		postFinder = setMatcher( postFinder, postSelector );
	}
	return markFunction(function( seed, results, context, xml ) {
		var temp, i, elem,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
			matcherIn = preFilter && ( seed || !selector ) ?
				condense( elems, preMap, preFilter, context, xml ) :
				elems,

			matcherOut = matcher ?
				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

					// ...intermediate processing is necessary
					[] :

					// ...otherwise use results directly
					results :
				matcherIn;

		// Find primary matches
		if ( matcher ) {
			matcher( matcherIn, matcherOut, context, xml );
		}

		// Apply postFilter
		if ( postFilter ) {
			temp = condense( matcherOut, postMap );
			postFilter( temp, [], context, xml );

			// Un-match failing elements by moving them back to matcherIn
			i = temp.length;
			while ( i-- ) {
				if ( (elem = temp[i]) ) {
					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
				}
			}
		}

		if ( seed ) {
			if ( postFinder || preFilter ) {
				if ( postFinder ) {
					// Get the final matcherOut by condensing this intermediate into postFinder contexts
					temp = [];
					i = matcherOut.length;
					while ( i-- ) {
						if ( (elem = matcherOut[i]) ) {
							// Restore matcherIn since elem is not yet a final match
							temp.push( (matcherIn[i] = elem) );
						}
					}
					postFinder( null, (matcherOut = []), temp, xml );
				}

				// Move matched elements from seed to results to keep them synchronized
				i = matcherOut.length;
				while ( i-- ) {
					if ( (elem = matcherOut[i]) &&
						(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {

						seed[temp] = !(results[temp] = elem);
					}
				}
			}

		// Add elements to results, through postFinder if defined
		} else {
			matcherOut = condense(
				matcherOut === results ?
					matcherOut.splice( preexisting, matcherOut.length ) :
					matcherOut
			);
			if ( postFinder ) {
				postFinder( null, results, matcherOut, xml );
			} else {
				push.apply( results, matcherOut );
			}
		}
	});
}

function matcherFromTokens( tokens ) {
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[0].type ],
		implicitRelative = leadingRelative || Expr.relative[" "],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf.call( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {
			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
				(checkContext = context).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );
		} ];

	for ( ; i < len; i++ ) {
		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
		} else {
			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

			// Return special upon seeing a positional matcher
			if ( matcher[ expando ] ) {
				// Find the next relative operator (if any) for proper handling
				j = ++i;
				for ( ; j < len; j++ ) {
					if ( Expr.relative[ tokens[j].type ] ) {
						break;
					}
				}
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector(
						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
					).replace( rtrim, "$1" ),
					matcher,
					i < j && matcherFromTokens( tokens.slice( i, j ) ),
					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
					j < len && toSelector( tokens )
				);
			}
			matchers.push( matcher );
		}
	}

	return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
	var bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, outermost ) {
			var elem, j, matcher,
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				setMatched = [],
				contextBackup = outermostContext,
				// We must always have either seed elements or outermost context
				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
				len = elems.length;

			if ( outermost ) {
				outermostContext = context !== document && context;
			}

			// Add elements passing elementMatchers directly to results
			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
			// Support: IE<9, Safari
			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;
					while ( (matcher = elementMatchers[j++]) ) {
						if ( matcher( elem, context, xml ) ) {
							results.push( elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
					}
				}

				// Track unmatched elements for set filters
				if ( bySet ) {
					// They will have gone through all possible matchers
					if ( (elem = !matcher && elem) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// Apply set filters to unmatched elements
			matchedCount += i;
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( (matcher = setMatchers[j++]) ) {
					matcher( unmatched, setMatched, context, xml );
				}

				if ( seed ) {
					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !(unmatched[i] || setMatched[i]) ) {
								setMatched[i] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
					setMatched = condense( setMatched );
				}

				// Add matches to results
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {

					Sizzle.uniqueSort( results );
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}

compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {
		// Generate a function of recursive functions that can be used to check each element
		if ( !match ) {
			match = tokenize( selector );
		}
		i = match.length;
		while ( i-- ) {
			cached = matcherFromTokens( match[i] );
			if ( cached[ expando ] ) {
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );

		// Save selector and tokenization
		cached.selector = selector;
	}
	return cached;
};

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {
	var i, tokens, token, type, find,
		compiled = typeof selector === "function" && selector,
		match = !seed && tokenize( (selector = compiled.selector || selector) );

	results = results || [];

	// Try to minimize operations if there is no seed and only one group
	if ( match.length === 1 ) {

		// Take a shortcut and set the context if the root selector is an ID
		tokens = match[0] = match[0].slice( 0 );
		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
				support.getById && context.nodeType === 9 && documentIsHTML &&
				Expr.relative[ tokens[1].type ] ) {

			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
			if ( !context ) {
				return results;

			// Precompiled matchers will still verify ancestry, so step up a level
			} else if ( compiled ) {
				context = context.parentNode;
			}

			selector = selector.slice( tokens.shift().value.length );
		}

		// Fetch a seed set for right-to-left matching
		i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
		while ( i-- ) {
			token = tokens[i];

			// Abort if we hit a combinator
			if ( Expr.relative[ (type = token.type) ] ) {
				break;
			}
			if ( (find = Expr.find[ type ]) ) {
				// Search, expanding context for leading sibling combinators
				if ( (seed = find(
					token.matches[0].replace( runescape, funescape ),
					rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
				)) ) {

					// If seed is empty or no tokens remain, we can return early
					tokens.splice( i, 1 );
					selector = seed.length && toSelector( tokens );
					if ( !selector ) {
						push.apply( results, seed );
						return results;
					}

					break;
				}
			}
		}
	}

	// Compile and execute a filtering function if one is not provided
	// Provide `match` to avoid retokenization if we modified the selector above
	( compiled || compile( selector, match ) )(
		seed,
		context,
		!documentIsHTML,
		results,
		rsibling.test( selector ) && testContext( context.parentNode ) || context
	);
	return results;
};

// One-time assignments

// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;

// Support: Chrome<14
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;

// Initialize against the default document
setDocument();

// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert(function( div1 ) {
	// Should return 1, but returns 4 (following)
	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});

// Support: IE<8
// Prevent attribute/property "interpolation"
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( div ) {
	div.innerHTML = "<a href='#'></a>";
	return div.firstChild.getAttribute("href") === "#" ;
}) ) {
	addHandle( "type|href|height|width", function( elem, name, isXML ) {
		if ( !isXML ) {
			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
		}
	});
}

// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( div ) {
	div.innerHTML = "<input/>";
	div.firstChild.setAttribute( "value", "" );
	return div.firstChild.getAttribute( "value" ) === "";
}) ) {
	addHandle( "value", function( elem, name, isXML ) {
		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
			return elem.defaultValue;
		}
	});
}

// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( div ) {
	return div.getAttribute("disabled") == null;
}) ) {
	addHandle( booleans, function( elem, name, isXML ) {
		var val;
		if ( !isXML ) {
			return elem[ name ] === true ? name.toLowerCase() :
					(val = elem.getAttributeNode( name )) && val.specified ?
					val.value :
				null;
		}
	});
}

return Sizzle;

})( window );



jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;



var rneedsContext = jQuery.expr.match.needsContext;

var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);



var risSimple = /^.[^:#\[\.,]*$/;

// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
	if ( jQuery.isFunction( qualifier ) ) {
		return jQuery.grep( elements, function( elem, i ) {
			/* jshint -W018 */
			return !!qualifier.call( elem, i, elem ) !== not;
		});

	}

	if ( qualifier.nodeType ) {
		return jQuery.grep( elements, function( elem ) {
			return ( elem === qualifier ) !== not;
		});

	}

	if ( typeof qualifier === "string" ) {
		if ( risSimple.test( qualifier ) ) {
			return jQuery.filter( qualifier, elements, not );
		}

		qualifier = jQuery.filter( qualifier, elements );
	}

	return jQuery.grep( elements, function( elem ) {
		return ( indexOf.call( qualifier, elem ) >= 0 ) !== not;
	});
}

jQuery.filter = function( expr, elems, not ) {
	var elem = elems[ 0 ];

	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	return elems.length === 1 && elem.nodeType === 1 ?
		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
			return elem.nodeType === 1;
		}));
};

jQuery.fn.extend({
	find: function( selector ) {
		var i,
			len = this.length,
			ret = [],
			self = this;

		if ( typeof selector !== "string" ) {
			return this.pushStack( jQuery( selector ).filter(function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			}) );
		}

		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, self[ i ], ret );
		}

		// Needed because $( selector, context ) becomes $( context ).find( selector )
		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
		ret.selector = this.selector ? this.selector + " " + selector : selector;
		return ret;
	},
	filter: function( selector ) {
		return this.pushStack( winnow(this, selector || [], false) );
	},
	not: function( selector ) {
		return this.pushStack( winnow(this, selector || [], true) );
	},
	is: function( selector ) {
		return !!winnow(
			this,

			// If this is a positional/relative selector, check membership in the returned set
			// so $("p:first").is("p:last") won't return true for a doc with two "p".
			typeof selector === "string" && rneedsContext.test( selector ) ?
				jQuery( selector ) :
				selector || [],
			false
		).length;
	}
});


// Initialize a jQuery object


// A central reference to the root jQuery(document)
var rootjQuery,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
	// Strict HTML recognition (#11290: must start with <)
	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,

	init = jQuery.fn.init = function( selector, context ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// Handle HTML strings
		if ( typeof selector === "string" ) {
			if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && (match[1] || !context) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[1] ) {
					context = context instanceof jQuery ? context[0] : context;

					// scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
					jQuery.merge( this, jQuery.parseHTML(
						match[1],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {
							// Properties of context are called as methods if possible
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[2] );

					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {
						// Inject the element directly into the jQuery object
						this.length = 1;
						this[0] = elem;
					}

					this.context = document;
					this.selector = selector;
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || rootjQuery ).find( selector );

			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			this.context = this[0] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) ) {
			return typeof rootjQuery.ready !== "undefined" ?
				rootjQuery.ready( selector ) :
				// Execute immediately if ready is not present
				selector( jQuery );
		}

		if ( selector.selector !== undefined ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return jQuery.makeArray( selector, this );
	};

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

// Initialize central reference
rootjQuery = jQuery( document );


var rparentsprev = /^(?:parents|prev(?:Until|All))/,
	// methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.extend({
	dir: function( elem, dir, until ) {
		var matched = [],
			truncate = until !== undefined;

		while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
			if ( elem.nodeType === 1 ) {
				if ( truncate && jQuery( elem ).is( until ) ) {
					break;
				}
				matched.push( elem );
			}
		}
		return matched;
	},

	sibling: function( n, elem ) {
		var matched = [];

		for ( ; n; n = n.nextSibling ) {
			if ( n.nodeType === 1 && n !== elem ) {
				matched.push( n );
			}
		}

		return matched;
	}
});

jQuery.fn.extend({
	has: function( target ) {
		var targets = jQuery( target, this ),
			l = targets.length;

		return this.filter(function() {
			var i = 0;
			for ( ; i < l; i++ ) {
				if ( jQuery.contains( this, targets[i] ) ) {
					return true;
				}
			}
		});
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			matched = [],
			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
				jQuery( selectors, context || this.context ) :
				0;

		for ( ; i < l; i++ ) {
			for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
				// Always skip document fragments
				if ( cur.nodeType < 11 && (pos ?
					pos.index(cur) > -1 :

					// Don't pass non-elements to Sizzle
					cur.nodeType === 1 &&
						jQuery.find.matchesSelector(cur, selectors)) ) {

					matched.push( cur );
					break;
				}
			}
		}

		return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
		}

		// index in selector
		if ( typeof elem === "string" ) {
			return indexOf.call( jQuery( elem ), this[ 0 ] );
		}

		// Locate the position of the desired element
		return indexOf.call( this,

			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[ 0 ] : elem
		);
	},

	add: function( selector, context ) {
		return this.pushStack(
			jQuery.unique(
				jQuery.merge( this.get(), jQuery( selector, context ) )
			)
		);
	},

	addBack: function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter(selector)
		);
	}
});

function sibling( cur, dir ) {
	while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
	return cur;
}

jQuery.each({
	parent: function( elem ) {
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null;
	},
	parents: function( elem ) {
		return jQuery.dir( elem, "parentNode" );
	},
	parentsUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "parentNode", until );
	},
	next: function( elem ) {
		return sibling( elem, "nextSibling" );
	},
	prev: function( elem ) {
		return sibling( elem, "previousSibling" );
	},
	nextAll: function( elem ) {
		return jQuery.dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) {
		return jQuery.dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) {
		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
	},
	children: function( elem ) {
		return jQuery.sibling( elem.firstChild );
	},
	contents: function( elem ) {
		return elem.contentDocument || jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var matched = jQuery.map( this, fn, until );

		if ( name.slice( -5 ) !== "Until" ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			matched = jQuery.filter( selector, matched );
		}

		if ( this.length > 1 ) {
			// Remove duplicates
			if ( !guaranteedUnique[ name ] ) {
				jQuery.unique( matched );
			}

			// Reverse order for parents* and prev-derivatives
			if ( rparentsprev.test( name ) ) {
				matched.reverse();
			}
		}

		return this.pushStack( matched );
	};
});
var rnotwhite = (/\S+/g);



// String to Object options format cache
var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
	var object = optionsCache[ options ] = {};
	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	});
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		( optionsCache[ options ] || createOptions( options ) ) :
		jQuery.extend( {}, options );

	var // Last fire value (for non-forgettable lists)
		memory,
		// Flag to know if list was already fired
		fired,
		// Flag to know if list is currently firing
		firing,
		// First callback to fire (used internally by add and fireWith)
		firingStart,
		// End of the loop when firing
		firingLength,
		// Index of currently firing callback (modified by remove if needed)
		firingIndex,
		// Actual callback list
		list = [],
		// Stack of fire calls for repeatable lists
		stack = !options.once && [],
		// Fire callbacks
		fire = function( data ) {
			memory = options.memory && data;
			fired = true;
			firingIndex = firingStart || 0;
			firingStart = 0;
			firingLength = list.length;
			firing = true;
			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
					memory = false; // To prevent further calls using add
					break;
				}
			}
			firing = false;
			if ( list ) {
				if ( stack ) {
					if ( stack.length ) {
						fire( stack.shift() );
					}
				} else if ( memory ) {
					list = [];
				} else {
					self.disable();
				}
			}
		},
		// Actual Callbacks object
		self = {
			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {
					// First, we save the current length
					var start = list.length;
					(function add( args ) {
						jQuery.each( args, function( _, arg ) {
							var type = jQuery.type( arg );
							if ( type === "function" ) {
								if ( !options.unique || !self.has( arg ) ) {
									list.push( arg );
								}
							} else if ( arg && arg.length && type !== "string" ) {
								// Inspect recursively
								add( arg );
							}
						});
					})( arguments );
					// Do we need to add the callbacks to the
					// current firing batch?
					if ( firing ) {
						firingLength = list.length;
					// With memory, if we're not firing then
					// we should call right away
					} else if ( memory ) {
						firingStart = start;
						fire( memory );
					}
				}
				return this;
			},
			// Remove a callback from the list
			remove: function() {
				if ( list ) {
					jQuery.each( arguments, function( _, arg ) {
						var index;
						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
							list.splice( index, 1 );
							// Handle firing indexes
							if ( firing ) {
								if ( index <= firingLength ) {
									firingLength--;
								}
								if ( index <= firingIndex ) {
									firingIndex--;
								}
							}
						}
					});
				}
				return this;
			},
			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
			},
			// Remove all callbacks from the list
			empty: function() {
				list = [];
				firingLength = 0;
				return this;
			},
			// Have the list do nothing anymore
			disable: function() {
				list = stack = memory = undefined;
				return this;
			},
			// Is it disabled?
			disabled: function() {
				return !list;
			},
			// Lock the list in its current state
			lock: function() {
				stack = undefined;
				if ( !memory ) {
					self.disable();
				}
				return this;
			},
			// Is it locked?
			locked: function() {
				return !stack;
			},
			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				if ( list && ( !fired || stack ) ) {
					args = args || [];
					args = [ context, args.slice ? args.slice() : args ];
					if ( firing ) {
						stack.push( args );
					} else {
						fire( args );
					}
				}
				return this;
			},
			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},
			// To know if the callbacks have already been called at least once
			fired: function() {
				return !!fired;
			}
		};

	return self;
};


jQuery.extend({

	Deferred: function( func ) {
		var tuples = [
				// action, add listener, listener list, final state
				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
				[ "notify", "progress", jQuery.Callbacks("memory") ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					return jQuery.Deferred(function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							deferred[ tuple[1] ](function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.done( newDefer.resolve )
										.fail( newDefer.reject )
										.progress( newDefer.notify );
								} else {
									newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
								}
							});
						});
						fns = null;
					}).promise();
				},
				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Keep pipe for back-compat
		promise.pipe = promise.then;

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 3 ];

			// promise[ done | fail | progress ] = list.add
			promise[ tuple[1] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add(function() {
					// state = [ resolved | rejected ]
					state = stateString;

				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}

			// deferred[ resolve | reject | notify ]
			deferred[ tuple[0] ] = function() {
				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
				return this;
			};
			deferred[ tuple[0] + "With" ] = list.fireWith;
		});

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( subordinate /* , ..., subordinateN */ ) {
		var i = 0,
			resolveValues = slice.call( arguments ),
			length = resolveValues.length,

			// the count of uncompleted subordinates
			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

			// Update function for both resolve and progress values
			updateFunc = function( i, contexts, values ) {
				return function( value ) {
					contexts[ i ] = this;
					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( values === progressValues ) {
						deferred.notifyWith( contexts, values );
					} else if ( !( --remaining ) ) {
						deferred.resolveWith( contexts, values );
					}
				};
			},

			progressValues, progressContexts, resolveContexts;

		// add listeners to Deferred subordinates; treat others as resolved
		if ( length > 1 ) {
			progressValues = new Array( length );
			progressContexts = new Array( length );
			resolveContexts = new Array( length );
			for ( ; i < length; i++ ) {
				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
					resolveValues[ i ].promise()
						.done( updateFunc( i, resolveContexts, resolveValues ) )
						.fail( deferred.reject )
						.progress( updateFunc( i, progressContexts, progressValues ) );
				} else {
					--remaining;
				}
			}
		}

		// if we're not waiting on anything, resolve the master
		if ( !remaining ) {
			deferred.resolveWith( resolveContexts, resolveValues );
		}

		return deferred.promise();
	}
});


// The deferred used on DOM ready
var readyList;

jQuery.fn.ready = function( fn ) {
	// Add the callback
	jQuery.ready.promise().done( fn );

	return this;
};

jQuery.extend({
	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Hold (or release) the ready event
	holdReady: function( hold ) {
		if ( hold ) {
			jQuery.readyWait++;
		} else {
			jQuery.ready( true );
		}
	},

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );

		// Trigger any bound ready events
		if ( jQuery.fn.triggerHandler ) {
			jQuery( document ).triggerHandler( "ready" );
			jQuery( document ).off( "ready" );
		}
	}
});

/**
 * The ready event handler and self cleanup method
 */
function completed() {
	document.removeEventListener( "DOMContentLoaded", completed, false );
	window.removeEventListener( "load", completed, false );
	jQuery.ready();
}

jQuery.ready.promise = function( obj ) {
	if ( !readyList ) {

		readyList = jQuery.Deferred();

		// Catch cases where $(document).ready() is called after the browser event has already occurred.
		// we once tried to use readyState "interactive" here, but it caused issues like the one
		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
		if ( document.readyState === "complete" ) {
			// Handle it asynchronously to allow scripts the opportunity to delay ready
			setTimeout( jQuery.ready );

		} else {

			// Use the handy event callback
			document.addEventListener( "DOMContentLoaded", completed, false );

			// A fallback to window.onload, that will always work
			window.addEventListener( "load", completed, false );
		}
	}
	return readyList.promise( obj );
};

// Kick off the DOM ready check even if the user does not
jQuery.ready.promise();




// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
	var i = 0,
		len = elems.length,
		bulk = key == null;

	// Sets many values
	if ( jQuery.type( key ) === "object" ) {
		chainable = true;
		for ( i in key ) {
			jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
		}

	// Sets one value
	} else if ( value !== undefined ) {
		chainable = true;

		if ( !jQuery.isFunction( value ) ) {
			raw = true;
		}

		if ( bulk ) {
			// Bulk operations run against the entire set
			if ( raw ) {
				fn.call( elems, value );
				fn = null;

			// ...except when executing function values
			} else {
				bulk = fn;
				fn = function( elem, key, value ) {
					return bulk.call( jQuery( elem ), value );
				};
			}
		}

		if ( fn ) {
			for ( ; i < len; i++ ) {
				fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
			}
		}
	}

	return chainable ?
		elems :

		// Gets
		bulk ?
			fn.call( elems ) :
			len ? fn( elems[0], key ) : emptyGet;
};


/**
 * Determines whether an object can have data
 */
jQuery.acceptData = function( owner ) {
	// Accepts only:
	//  - Node
	//    - Node.ELEMENT_NODE
	//    - Node.DOCUMENT_NODE
	//  - Object
	//    - Any
	/* jshint -W018 */
	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};


function Data() {
	// Support: Android < 4,
	// Old WebKit does not have Object.preventExtensions/freeze method,
	// return new empty object instead with no [[set]] accessor
	Object.defineProperty( this.cache = {}, 0, {
		get: function() {
			return {};
		}
	});

	this.expando = jQuery.expando + Math.random();
}

Data.uid = 1;
Data.accepts = jQuery.acceptData;

Data.prototype = {
	key: function( owner ) {
		// We can accept data for non-element nodes in modern browsers,
		// but we should not, see #8335.
		// Always return the key for a frozen object.
		if ( !Data.accepts( owner ) ) {
			return 0;
		}

		var descriptor = {},
			// Check if the owner object already has a cache key
			unlock = owner[ this.expando ];

		// If not, create one
		if ( !unlock ) {
			unlock = Data.uid++;

			// Secure it in a non-enumerable, non-writable property
			try {
				descriptor[ this.expando ] = { value: unlock };
				Object.defineProperties( owner, descriptor );

			// Support: Android < 4
			// Fallback to a less secure definition
			} catch ( e ) {
				descriptor[ this.expando ] = unlock;
				jQuery.extend( owner, descriptor );
			}
		}

		// Ensure the cache object
		if ( !this.cache[ unlock ] ) {
			this.cache[ unlock ] = {};
		}

		return unlock;
	},
	set: function( owner, data, value ) {
		var prop,
			// There may be an unlock assigned to this node,
			// if there is no entry for this "owner", create one inline
			// and set the unlock as though an owner entry had always existed
			unlock = this.key( owner ),
			cache = this.cache[ unlock ];

		// Handle: [ owner, key, value ] args
		if ( typeof data === "string" ) {
			cache[ data ] = value;

		// Handle: [ owner, { properties } ] args
		} else {
			// Fresh assignments by object are shallow copied
			if ( jQuery.isEmptyObject( cache ) ) {
				jQuery.extend( this.cache[ unlock ], data );
			// Otherwise, copy the properties one-by-one to the cache object
			} else {
				for ( prop in data ) {
					cache[ prop ] = data[ prop ];
				}
			}
		}
		return cache;
	},
	get: function( owner, key ) {
		// Either a valid cache is found, or will be created.
		// New caches will be created and the unlock returned,
		// allowing direct access to the newly created
		// empty data object. A valid owner object must be provided.
		var cache = this.cache[ this.key( owner ) ];

		return key === undefined ?
			cache : cache[ key ];
	},
	access: function( owner, key, value ) {
		var stored;
		// In cases where either:
		//
		//   1. No key was specified
		//   2. A string key was specified, but no value provided
		//
		// Take the "read" path and allow the get method to determine
		// which value to return, respectively either:
		//
		//   1. The entire cache object
		//   2. The data stored at the key
		//
		if ( key === undefined ||
				((key && typeof key === "string") && value === undefined) ) {

			stored = this.get( owner, key );

			return stored !== undefined ?
				stored : this.get( owner, jQuery.camelCase(key) );
		}

		// [*]When the key is not a string, or both a key and value
		// are specified, set or extend (existing objects) with either:
		//
		//   1. An object of properties
		//   2. A key and value
		//
		this.set( owner, key, value );

		// Since the "set" path can have two possible entry points
		// return the expected data based on which path was taken[*]
		return value !== undefined ? value : key;
	},
	remove: function( owner, key ) {
		var i, name, camel,
			unlock = this.key( owner ),
			cache = this.cache[ unlock ];

		if ( key === undefined ) {
			this.cache[ unlock ] = {};

		} else {
			// Support array or space separated string of keys
			if ( jQuery.isArray( key ) ) {
				// If "name" is an array of keys...
				// When data is initially created, via ("key", "val") signature,
				// keys will be converted to camelCase.
				// Since there is no way to tell _how_ a key was added, remove
				// both plain key and camelCase key. #12786
				// This will only penalize the array argument path.
				name = key.concat( key.map( jQuery.camelCase ) );
			} else {
				camel = jQuery.camelCase( key );
				// Try the string as a key before any manipulation
				if ( key in cache ) {
					name = [ key, camel ];
				} else {
					// If a key with the spaces exists, use it.
					// Otherwise, create an array by matching non-whitespace
					name = camel;
					name = name in cache ?
						[ name ] : ( name.match( rnotwhite ) || [] );
				}
			}

			i = name.length;
			while ( i-- ) {
				delete cache[ name[ i ] ];
			}
		}
	},
	hasData: function( owner ) {
		return !jQuery.isEmptyObject(
			this.cache[ owner[ this.expando ] ] || {}
		);
	},
	discard: function( owner ) {
		if ( owner[ this.expando ] ) {
			delete this.cache[ owner[ this.expando ] ];
		}
	}
};
var data_priv = new Data();

var data_user = new Data();



/*
	Implementation Summary

	1. Enforce API surface and semantic compatibility with 1.9.x branch
	2. Improve the module's maintainability by reducing the storage
		paths to a single mechanism.
	3. Use the same single mechanism to support "private" and "user" data.
	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
	5. Avoid exposing implementation details on user objects (eg. expando properties)
	6. Provide a clear path for implementation upgrade to WeakMap in 2014
*/
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
	rmultiDash = /([A-Z])/g;

function dataAttr( elem, key, data ) {
	var name;

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {
		name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = data === "true" ? true :
					data === "false" ? false :
					data === "null" ? null :
					// Only convert to a number if it doesn't change the string
					+data + "" === data ? +data :
					rbrace.test( data ) ? jQuery.parseJSON( data ) :
					data;
			} catch( e ) {}

			// Make sure we set the data so it isn't changed later
			data_user.set( elem, key, data );
		} else {
			data = undefined;
		}
	}
	return data;
}

jQuery.extend({
	hasData: function( elem ) {
		return data_user.hasData( elem ) || data_priv.hasData( elem );
	},

	data: function( elem, name, data ) {
		return data_user.access( elem, name, data );
	},

	removeData: function( elem, name ) {
		data_user.remove( elem, name );
	},

	// TODO: Now that all calls to _data and _removeData have been replaced
	// with direct calls to data_priv methods, these can be deprecated.
	_data: function( elem, name, data ) {
		return data_priv.access( elem, name, data );
	},

	_removeData: function( elem, name ) {
		data_priv.remove( elem, name );
	}
});

jQuery.fn.extend({
	data: function( key, value ) {
		var i, name, data,
			elem = this[ 0 ],
			attrs = elem && elem.attributes;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = data_user.get( elem );

				if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
					i = attrs.length;
					while ( i-- ) {

						// Support: IE11+
						// The attrs elements can be null (#14894)
						if ( attrs[ i ] ) {
							name = attrs[ i ].name;
							if ( name.indexOf( "data-" ) === 0 ) {
								name = jQuery.camelCase( name.slice(5) );
								dataAttr( elem, name, data[ name ] );
							}
						}
					}
					data_priv.set( elem, "hasDataAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each(function() {
				data_user.set( this, key );
			});
		}

		return access( this, function( value ) {
			var data,
				camelKey = jQuery.camelCase( key );

			// The calling jQuery object (element matches) is not empty
			// (and therefore has an element appears at this[ 0 ]) and the
			// `value` parameter was not undefined. An empty jQuery object
			// will result in `undefined` for elem = this[ 0 ] which will
			// throw an exception if an attempt to read a data cache is made.
			if ( elem && value === undefined ) {
				// Attempt to get data from the cache
				// with the key as-is
				data = data_user.get( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to get data from the cache
				// with the key camelized
				data = data_user.get( elem, camelKey );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to "discover" the data in
				// HTML5 custom data-* attrs
				data = dataAttr( elem, camelKey, undefined );
				if ( data !== undefined ) {
					return data;
				}

				// We tried really hard, but the data doesn't exist.
				return;
			}

			// Set the data...
			this.each(function() {
				// First, attempt to store a copy or reference of any
				// data that might've been store with a camelCased key.
				var data = data_user.get( this, camelKey );

				// For HTML5 data-* attribute interop, we have to
				// store property names with dashes in a camelCase form.
				// This might not apply to all properties...*
				data_user.set( this, camelKey, value );

				// *... In the case of properties that might _actually_
				// have dashes, we need to also store a copy of that
				// unchanged property.
				if ( key.indexOf("-") !== -1 && data !== undefined ) {
					data_user.set( this, key, value );
				}
			});
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each(function() {
			data_user.remove( this, key );
		});
	}
});


jQuery.extend({
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = data_priv.get( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || jQuery.isArray( data ) ) {
					queue = data_priv.access( elem, type, jQuery.makeArray(data) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// not intended for public consumption - generates a queueHooks object, or returns the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return data_priv.get( elem, key ) || data_priv.access( elem, key, {
			empty: jQuery.Callbacks("once memory").add(function() {
				data_priv.remove( elem, [ type + "queue", key ] );
			})
		});
	}
});

jQuery.fn.extend({
	queue: function( type, data ) {
		var setter = 2;

		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}

		if ( arguments.length < setter ) {
			return jQuery.queue( this[0], type );
		}

		return data === undefined ?
			this :
			this.each(function() {
				var queue = jQuery.queue( this, type, data );

				// ensure a hooks for this queue
				jQuery._queueHooks( this, type );

				if ( type === "fx" && queue[0] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}
			});
	},
	dequeue: function( type ) {
		return this.each(function() {
			jQuery.dequeue( this, type );
		});
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	},
	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),
			elements = this,
			i = this.length,
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) {
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while ( i-- ) {
			tmp = data_priv.get( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
});
var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;

var cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var isHidden = function( elem, el ) {
		// isHidden might be called from jQuery#filter function;
		// in that case, element will be second argument
		elem = el || elem;
		return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
	};

var rcheckableType = (/^(?:checkbox|radio)$/i);



(function() {
	var fragment = document.createDocumentFragment(),
		div = fragment.appendChild( document.createElement( "div" ) ),
		input = document.createElement( "input" );

	// #11217 - WebKit loses check when the name is after the checked attribute
	// Support: Windows Web Apps (WWA)
	// `name` and `type` need .setAttribute for WWA
	input.setAttribute( "type", "radio" );
	input.setAttribute( "checked", "checked" );
	input.setAttribute( "name", "t" );

	div.appendChild( input );

	// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
	// old WebKit doesn't clone checked state correctly in fragments
	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Make sure textarea (and checkbox) defaultValue is properly cloned
	// Support: IE9-IE11+
	div.innerHTML = "<textarea>x</textarea>";
	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
})();
var strundefined = typeof undefined;



support.focusinBubbles = "onfocusin" in window;


var
	rkeyEvent = /^key/,
	rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

function safeActiveElement() {
	try {
		return document.activeElement;
	} catch ( err ) { }
}

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	global: {},

	add: function( elem, types, handler, data, selector ) {

		var handleObjIn, eventHandle, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = data_priv.get( elem );

		// Don't attach events to noData or text/comment nodes (but allow plain objects)
		if ( !elemData ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !(events = elemData.events) ) {
			events = elemData.events = {};
		}
		if ( !(eventHandle = elemData.handle) ) {
			eventHandle = elemData.handle = function( e ) {
				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};
		}

		// Handle multiple events separated by a space
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[t] ) || [];
			type = origType = tmp[1];
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			// There *must* be a type, no attaching namespace-only handlers
			if ( !type ) {
				continue;
			}

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend({
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join(".")
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !(handlers = events[ type ]) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener if the special events handler returns false
				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle, false );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {

		var j, origCount, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = data_priv.hasData( elem ) && data_priv.get( elem );

		if ( !elemData || !(events = elemData.events) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[t] ) || [];
			type = origType = tmp[1];
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			delete elemData.handle;
			data_priv.remove( elem, "events" );
		}
	},

	trigger: function( event, data, elem, onlyHandlers ) {

		var i, cur, tmp, bubbleType, ontype, handle, special,
			eventPath = [ elem || document ],
			type = hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];

		cur = tmp = elem = elem || document;

		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		if ( type.indexOf(".") >= 0 ) {
			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split(".");
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf(":") < 0 && "on" + type;

		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join(".");
		event.namespace_re = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
			null;

		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === (elem.ownerDocument || document) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// Fire handlers on the event path
		i = 0;
		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {

			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// jQuery handler
			handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
				event.result = handle.apply( cur, data );
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
				jQuery.acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name name as the event.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];

					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					elem[ type ]();
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	dispatch: function( event ) {

		// Make a writable jQuery.Event from the native event object
		event = jQuery.event.fix( event );

		var i, j, ret, matched, handleObj,
			handlerQueue = [],
			args = slice.call( arguments ),
			handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[0] = event;
		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {

				// Triggered event must either 1) have no namespace, or
				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {

					event.handleObj = handleObj;
					event.data = handleObj.data;

					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
							.apply( matched.elem, args );

					if ( ret !== undefined ) {
						if ( (event.result = ret) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		// Call the postDispatch hook for the mapped type
		if ( special.postDispatch ) {
			special.postDispatch.call( this, event );
		}

		return event.result;
	},

	handlers: function( event, handlers ) {
		var i, matches, sel, handleObj,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Find delegate handlers
		// Black-hole SVG <use> instance trees (#13180)
		// Avoid non-left-click bubbling in Firefox (#3861)
		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {

			for ( ; cur !== this; cur = cur.parentNode || this ) {

				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.disabled !== true || event.type !== "click" ) {
					matches = [];
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (#13203)
						sel = handleObj.selector + " ";

						if ( matches[ sel ] === undefined ) {
							matches[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) >= 0 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matches[ sel ] ) {
							matches.push( handleObj );
						}
					}
					if ( matches.length ) {
						handlerQueue.push({ elem: cur, handlers: matches });
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		if ( delegateCount < handlers.length ) {
			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
		}

		return handlerQueue;
	},

	// Includes some event props shared by KeyEvent and MouseEvent
	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),

	fixHooks: {},

	keyHooks: {
		props: "char charCode key keyCode".split(" "),
		filter: function( event, original ) {

			// Add which for key events
			if ( event.which == null ) {
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}

			return event;
		}
	},

	mouseHooks: {
		props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
		filter: function( event, original ) {
			var eventDoc, doc, body,
				button = original.button;

			// Calculate pageX/Y if missing and clientX/Y available
			if ( event.pageX == null && original.clientX != null ) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			// Add which for click: 1 === left; 2 === middle; 3 === right
			// Note: button is not normalized, so don't use it
			if ( !event.which && button !== undefined ) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	},

	fix: function( event ) {
		if ( event[ jQuery.expando ] ) {
			return event;
		}

		// Create a writable copy of the event object and normalize some properties
		var i, prop, copy,
			type = event.type,
			originalEvent = event,
			fixHook = this.fixHooks[ type ];

		if ( !fixHook ) {
			this.fixHooks[ type ] = fixHook =
				rmouseEvent.test( type ) ? this.mouseHooks :
				rkeyEvent.test( type ) ? this.keyHooks :
				{};
		}
		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

		event = new jQuery.Event( originalEvent );

		i = copy.length;
		while ( i-- ) {
			prop = copy[ i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Support: Cordova 2.5 (WebKit) (#13255)
		// All events should have a target; Cordova deviceready doesn't
		if ( !event.target ) {
			event.target = document;
		}

		// Support: Safari 6.0+, Chrome < 28
		// Target should not be a text node (#504, #13143)
		if ( event.target.nodeType === 3 ) {
			event.target = event.target.parentNode;
		}

		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
	},

	special: {
		load: {
			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		focus: {
			// Fire native event if possible so blur/focus sequence is correct
			trigger: function() {
				if ( this !== safeActiveElement() && this.focus ) {
					this.focus();
					return false;
				}
			},
			delegateType: "focusin"
		},
		blur: {
			trigger: function() {
				if ( this === safeActiveElement() && this.blur ) {
					this.blur();
					return false;
				}
			},
			delegateType: "focusout"
		},
		click: {
			// For checkbox, fire native event so checked state will be right
			trigger: function() {
				if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
					this.click();
					return false;
				}
			},

			// For cross-browser consistency, don't fire native .click() on links
			_default: function( event ) {
				return jQuery.nodeName( event.target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	},

	simulate: function( type, elem, event, bubble ) {
		// Piggyback on a donor event to simulate a different one.
		// Fake originalEvent to avoid donor's stopPropagation, but if the
		// simulated event prevents default then we do the same on the donor.
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{
				type: type,
				isSimulated: true,
				originalEvent: {}
			}
		);
		if ( bubble ) {
			jQuery.event.trigger( e, null, elem );
		} else {
			jQuery.event.dispatch.call( elem, e );
		}
		if ( e.isDefaultPrevented() ) {
			event.preventDefault();
		}
	}
};

jQuery.removeEvent = function( elem, type, handle ) {
	if ( elem.removeEventListener ) {
		elem.removeEventListener( type, handle, false );
	}
};

jQuery.Event = function( src, props ) {
	// Allow instantiation without the 'new' keyword
	if ( !(this instanceof jQuery.Event) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
	if ( src && src.type ) {
		this.originalEvent = src;
		this.type = src.type;

		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&
				// Support: Android < 4.0
				src.returnValue === false ?
			returnTrue :
			returnFalse;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || jQuery.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;

		if ( e && e.preventDefault ) {
			e.preventDefault();
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;

		if ( e && e.stopPropagation ) {
			e.stopPropagation();
		}
	},
	stopImmediatePropagation: function() {
		var e = this.originalEvent;

		this.isImmediatePropagationStopped = returnTrue;

		if ( e && e.stopImmediatePropagation ) {
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// Create mouseenter/leave events using mouseover/out and event-time checks
// Support: Chrome 15+
jQuery.each({
	mouseenter: "mouseover",
	mouseleave: "mouseout",
	pointerenter: "pointerover",
	pointerleave: "pointerout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var ret,
				target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj;

			// For mousenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
});

// Create "bubbling" focus and blur events
// Support: Firefox, Chrome, Safari
if ( !support.focusinBubbles ) {
	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {

		// Attach a single capturing handler on the document while someone wants focusin/focusout
		var handler = function( event ) {
				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
			};

		jQuery.event.special[ fix ] = {
			setup: function() {
				var doc = this.ownerDocument || this,
					attaches = data_priv.access( doc, fix );

				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				data_priv.access( doc, fix, ( attaches || 0 ) + 1 );
			},
			teardown: function() {
				var doc = this.ownerDocument || this,
					attaches = data_priv.access( doc, fix ) - 1;

				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					data_priv.remove( doc, fix );

				} else {
					data_priv.access( doc, fix, attaches );
				}
			}
		};
	});
}

jQuery.fn.extend({

	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
		var origFn, type;

		// Types can be a map of types/handlers
		if ( typeof types === "object" ) {
			// ( types-Object, selector, data )
			if ( typeof selector !== "string" ) {
				// ( types-Object, data )
				data = data || selector;
				selector = undefined;
			}
			for ( type in types ) {
				this.on( type, selector, data, types[ type ], one );
			}
			return this;
		}

		if ( data == null && fn == null ) {
			// ( types, fn )
			fn = selector;
			data = selector = undefined;
		} else if ( fn == null ) {
			if ( typeof selector === "string" ) {
				// ( types, selector, fn )
				fn = data;
				data = undefined;
			} else {
				// ( types, data, fn )
				fn = data;
				data = selector;
				selector = undefined;
			}
		}
		if ( fn === false ) {
			fn = returnFalse;
		} else if ( !fn ) {
			return this;
		}

		if ( one === 1 ) {
			origFn = fn;
			fn = function( event ) {
				// Can use an empty set, since event contains the info
				jQuery().off( event );
				return origFn.apply( this, arguments );
			};
			// Use same guid so caller can remove using origFn
			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
		}
		return this.each( function() {
			jQuery.event.add( this, types, fn, data, selector );
		});
	},
	one: function( types, selector, data, fn ) {
		return this.on( types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
		var handleObj, type;
		if ( types && types.preventDefault && types.handleObj ) {
			// ( event )  dispatched jQuery.Event
			handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
		if ( typeof types === "object" ) {
			// ( types-object [, selector] )
			for ( type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
		if ( selector === false || typeof selector === "function" ) {
			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
		return this.each(function() {
			jQuery.event.remove( this, types, fn, selector );
		});
	},

	trigger: function( type, data ) {
		return this.each(function() {
			jQuery.event.trigger( type, data, this );
		});
	},
	triggerHandler: function( type, data ) {
		var elem = this[0];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
});


var
	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
	rtagName = /<([\w:]+)/,
	rhtml = /<|&#?\w+;/,
	rnoInnerhtml = /<(?:script|style|link)/i,
	// checked="checked" or checked
	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
	rscriptType = /^$|\/(?:java|ecma)script/i,
	rscriptTypeMasked = /^true\/(.*)/,
	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,

	// We have to close these tags to support XHTML (#13200)
	wrapMap = {

		// Support: IE 9
		option: [ 1, "<select multiple='multiple'>", "</select>" ],

		thead: [ 1, "<table>", "</table>" ],
		col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

		_default: [ 0, "", "" ]
	};

// Support: IE 9
wrapMap.optgroup = wrapMap.option;

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;

// Support: 1.x compatibility
// Manipulating tables requires a tbody
function manipulationTarget( elem, content ) {
	return jQuery.nodeName( elem, "table" ) &&
		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?

		elem.getElementsByTagName("tbody")[0] ||
			elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
		elem;
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type;
	return elem;
}
function restoreScript( elem ) {
	var match = rscriptTypeMasked.exec( elem.type );

	if ( match ) {
		elem.type = match[ 1 ];
	} else {
		elem.removeAttribute("type");
	}

	return elem;
}

// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		data_priv.set(
			elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
		);
	}
}

function cloneCopyEvent( src, dest ) {
	var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;

	if ( dest.nodeType !== 1 ) {
		return;
	}

	// 1. Copy private data: events, handlers, etc.
	if ( data_priv.hasData( src ) ) {
		pdataOld = data_priv.access( src );
		pdataCur = data_priv.set( dest, pdataOld );
		events = pdataOld.events;

		if ( events ) {
			delete pdataCur.handle;
			pdataCur.events = {};

			for ( type in events ) {
				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
					jQuery.event.add( dest, type, events[ type ][ i ] );
				}
			}
		}
	}

	// 2. Copy user data
	if ( data_user.hasData( src ) ) {
		udataOld = data_user.access( src );
		udataCur = jQuery.extend( {}, udataOld );

		data_user.set( dest, udataCur );
	}
}

function getAll( context, tag ) {
	var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
			context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
			[];

	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
		jQuery.merge( [ context ], ret ) :
		ret;
}

// Support: IE >= 9
function fixInput( src, dest ) {
	var nodeName = dest.nodeName.toLowerCase();

	// Fails to persist the checked state of a cloned checkbox or radio button.
	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
		dest.checked = src.checked;

	// Fails to return the selected option to the default selected state when cloning options
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

jQuery.extend({
	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
		var i, l, srcElements, destElements,
			clone = elem.cloneNode( true ),
			inPage = jQuery.contains( elem.ownerDocument, elem );

		// Support: IE >= 9
		// Fix Cloning issues
		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
				!jQuery.isXMLDoc( elem ) ) {

			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			for ( i = 0, l = srcElements.length; i < l; i++ ) {
				fixInput( srcElements[ i ], destElements[ i ] );
			}
		}

		// Copy the events from the original to the clone
		if ( dataAndEvents ) {
			if ( deepDataAndEvents ) {
				srcElements = srcElements || getAll( elem );
				destElements = destElements || getAll( clone );

				for ( i = 0, l = srcElements.length; i < l; i++ ) {
					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		// Return the cloned set
		return clone;
	},

	buildFragment: function( elems, context, scripts, selection ) {
		var elem, tmp, tag, wrap, contains, j,
			fragment = context.createDocumentFragment(),
			nodes = [],
			i = 0,
			l = elems.length;

		for ( ; i < l; i++ ) {
			elem = elems[ i ];

			if ( elem || elem === 0 ) {

				// Add nodes directly
				if ( jQuery.type( elem ) === "object" ) {
					// Support: QtWebKit
					// jQuery.merge because push.apply(_, arraylike) throws
					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

				// Convert non-html into a text node
				} else if ( !rhtml.test( elem ) ) {
					nodes.push( context.createTextNode( elem ) );

				// Convert html into DOM nodes
				} else {
					tmp = tmp || fragment.appendChild( context.createElement("div") );

					// Deserialize a standard representation
					tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
					wrap = wrapMap[ tag ] || wrapMap._default;
					tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];

					// Descend through wrappers to the right content
					j = wrap[ 0 ];
					while ( j-- ) {
						tmp = tmp.lastChild;
					}

					// Support: QtWebKit
					// jQuery.merge because push.apply(_, arraylike) throws
					jQuery.merge( nodes, tmp.childNodes );

					// Remember the top-level container
					tmp = fragment.firstChild;

					// Fixes #12346
					// Support: Webkit, IE
					tmp.textContent = "";
				}
			}
		}

		// Remove wrapper from fragment
		fragment.textContent = "";

		i = 0;
		while ( (elem = nodes[ i++ ]) ) {

			// #4087 - If origin and destination elements are the same, and this is
			// that element, do not do anything
			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
				continue;
			}

			contains = jQuery.contains( elem.ownerDocument, elem );

			// Append to fragment
			tmp = getAll( fragment.appendChild( elem ), "script" );

			// Preserve script evaluation history
			if ( contains ) {
				setGlobalEval( tmp );
			}

			// Capture executables
			if ( scripts ) {
				j = 0;
				while ( (elem = tmp[ j++ ]) ) {
					if ( rscriptType.test( elem.type || "" ) ) {
						scripts.push( elem );
					}
				}
			}
		}

		return fragment;
	},

	cleanData: function( elems ) {
		var data, elem, type, key,
			special = jQuery.event.special,
			i = 0;

		for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
			if ( jQuery.acceptData( elem ) ) {
				key = elem[ data_priv.expando ];

				if ( key && (data = data_priv.cache[ key ]) ) {
					if ( data.events ) {
						for ( type in data.events ) {
							if ( special[ type ] ) {
								jQuery.event.remove( elem, type );

							// This is a shortcut to avoid jQuery.event.remove's overhead
							} else {
								jQuery.removeEvent( elem, type, data.handle );
							}
						}
					}
					if ( data_priv.cache[ key ] ) {
						// Discard any remaining `private` data
						delete data_priv.cache[ key ];
					}
				}
			}
			// Discard any remaining `user` data
			delete data_user.cache[ elem[ data_user.expando ] ];
		}
	}
});

jQuery.fn.extend({
	text: function( value ) {
		return access( this, function( value ) {
			return value === undefined ?
				jQuery.text( this ) :
				this.empty().each(function() {
					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
						this.textContent = value;
					}
				});
		}, null, value, arguments.length );
	},

	append: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.appendChild( elem );
			}
		});
	},

	prepend: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.insertBefore( elem, target.firstChild );
			}
		});
	},

	before: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this );
			}
		});
	},

	after: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this.nextSibling );
			}
		});
	},

	remove: function( selector, keepData /* Internal Use Only */ ) {
		var elem,
			elems = selector ? jQuery.filter( selector, this ) : this,
			i = 0;

		for ( ; (elem = elems[i]) != null; i++ ) {
			if ( !keepData && elem.nodeType === 1 ) {
				jQuery.cleanData( getAll( elem ) );
			}

			if ( elem.parentNode ) {
				if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
					setGlobalEval( getAll( elem, "script" ) );
				}
				elem.parentNode.removeChild( elem );
			}
		}

		return this;
	},

	empty: function() {
		var elem,
			i = 0;

		for ( ; (elem = this[i]) != null; i++ ) {
			if ( elem.nodeType === 1 ) {

				// Prevent memory leaks
				jQuery.cleanData( getAll( elem, false ) );

				// Remove any remaining nodes
				elem.textContent = "";
			}
		}

		return this;
	},

	clone: function( dataAndEvents, deepDataAndEvents ) {
		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

		return this.map(function() {
			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
		});
	},

	html: function( value ) {
		return access( this, function( value ) {
			var elem = this[ 0 ] || {},
				i = 0,
				l = this.length;

			if ( value === undefined && elem.nodeType === 1 ) {
				return elem.innerHTML;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {

				value = value.replace( rxhtmlTag, "<$1></$2>" );

				try {
					for ( ; i < l; i++ ) {
						elem = this[ i ] || {};

						// Remove element nodes and prevent memory leaks
						if ( elem.nodeType === 1 ) {
							jQuery.cleanData( getAll( elem, false ) );
							elem.innerHTML = value;
						}
					}

					elem = 0;

				// If using innerHTML throws an exception, use the fallback method
				} catch( e ) {}
			}

			if ( elem ) {
				this.empty().append( value );
			}
		}, null, value, arguments.length );
	},

	replaceWith: function() {
		var arg = arguments[ 0 ];

		// Make the changes, replacing each context element with the new content
		this.domManip( arguments, function( elem ) {
			arg = this.parentNode;

			jQuery.cleanData( getAll( this ) );

			if ( arg ) {
				arg.replaceChild( elem, this );
			}
		});

		// Force removal if there was no new content (e.g., from empty arguments)
		return arg && (arg.length || arg.nodeType) ? this : this.remove();
	},

	detach: function( selector ) {
		return this.remove( selector, true );
	},

	domManip: function( args, callback ) {

		// Flatten any nested arrays
		args = concat.apply( [], args );

		var fragment, first, scripts, hasScripts, node, doc,
			i = 0,
			l = this.length,
			set = this,
			iNoClone = l - 1,
			value = args[ 0 ],
			isFunction = jQuery.isFunction( value );

		// We can't cloneNode fragments that contain checked, in WebKit
		if ( isFunction ||
				( l > 1 && typeof value === "string" &&
					!support.checkClone && rchecked.test( value ) ) ) {
			return this.each(function( index ) {
				var self = set.eq( index );
				if ( isFunction ) {
					args[ 0 ] = value.call( this, index, self.html() );
				}
				self.domManip( args, callback );
			});
		}

		if ( l ) {
			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
			first = fragment.firstChild;

			if ( fragment.childNodes.length === 1 ) {
				fragment = first;
			}

			if ( first ) {
				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
				hasScripts = scripts.length;

				// Use the original fragment for the last item instead of the first because it can end up
				// being emptied incorrectly in certain situations (#8070).
				for ( ; i < l; i++ ) {
					node = fragment;

					if ( i !== iNoClone ) {
						node = jQuery.clone( node, true, true );

						// Keep references to cloned scripts for later restoration
						if ( hasScripts ) {
							// Support: QtWebKit
							// jQuery.merge because push.apply(_, arraylike) throws
							jQuery.merge( scripts, getAll( node, "script" ) );
						}
					}

					callback.call( this[ i ], node, i );
				}

				if ( hasScripts ) {
					doc = scripts[ scripts.length - 1 ].ownerDocument;

					// Reenable scripts
					jQuery.map( scripts, restoreScript );

					// Evaluate executable scripts on first document insertion
					for ( i = 0; i < hasScripts; i++ ) {
						node = scripts[ i ];
						if ( rscriptType.test( node.type || "" ) &&
							!data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {

							if ( node.src ) {
								// Optional AJAX dependency, but won't run scripts if not present
								if ( jQuery._evalUrl ) {
									jQuery._evalUrl( node.src );
								}
							} else {
								jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
							}
						}
					}
				}
			}
		}

		return this;
	}
});

jQuery.each({
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function( name, original ) {
	jQuery.fn[ name ] = function( selector ) {
		var elems,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1,
			i = 0;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone( true );
			jQuery( insert[ i ] )[ original ]( elems );

			// Support: QtWebKit
			// .get() because push.apply(_, arraylike) throws
			push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
});


var iframe,
	elemdisplay = {};

/**
 * Retrieve the actual display of a element
 * @param {String} name nodeName of the element
 * @param {Object} doc Document object
 */
// Called only from within defaultDisplay
function actualDisplay( name, doc ) {
	var style,
		elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),

		// getDefaultComputedStyle might be reliably used only on attached element
		display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ?

			// Use of this method is a temporary fix (more like optmization) until something better comes along,
			// since it was removed from specification and supported only in FF
			style.display : jQuery.css( elem[ 0 ], "display" );

	// We don't have any data stored on the element,
	// so use "detach" method as fast way to get rid of the element
	elem.detach();

	return display;
}

/**
 * Try to determine the default display value of an element
 * @param {String} nodeName
 */
function defaultDisplay( nodeName ) {
	var doc = document,
		display = elemdisplay[ nodeName ];

	if ( !display ) {
		display = actualDisplay( nodeName, doc );

		// If the simple way fails, read from inside an iframe
		if ( display === "none" || !display ) {

			// Use the already-created iframe if possible
			iframe = (iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" )).appendTo( doc.documentElement );

			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
			doc = iframe[ 0 ].contentDocument;

			// Support: IE
			doc.write();
			doc.close();

			display = actualDisplay( nodeName, doc );
			iframe.detach();
		}

		// Store the correct default display
		elemdisplay[ nodeName ] = display;
	}

	return display;
}
var rmargin = (/^margin/);

var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

var getStyles = function( elem ) {
		return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
	};



function curCSS( elem, name, computed ) {
	var width, minWidth, maxWidth, ret,
		style = elem.style;

	computed = computed || getStyles( elem );

	// Support: IE9
	// getPropertyValue is only needed for .css('filter') in IE9, see #12537
	if ( computed ) {
		ret = computed.getPropertyValue( name ) || computed[ name ];
	}

	if ( computed ) {

		if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
			ret = jQuery.style( elem, name );
		}

		// Support: iOS < 6
		// A tribute to the "awesome hack by Dean Edwards"
		// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
		// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
		if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {

			// Remember the original values
			width = style.width;
			minWidth = style.minWidth;
			maxWidth = style.maxWidth;

			// Put in the new values to get a computed value out
			style.minWidth = style.maxWidth = style.width = ret;
			ret = computed.width;

			// Revert the changed values
			style.width = width;
			style.minWidth = minWidth;
			style.maxWidth = maxWidth;
		}
	}

	return ret !== undefined ?
		// Support: IE
		// IE returns zIndex value as an integer.
		ret + "" :
		ret;
}


function addGetHookIf( conditionFn, hookFn ) {
	// Define the hook, we'll check on the first run if it's really needed.
	return {
		get: function() {
			if ( conditionFn() ) {
				// Hook not needed (or it's not possible to use it due to missing dependency),
				// remove it.
				// Since there are no other hooks for marginRight, remove the whole object.
				delete this.get;
				return;
			}

			// Hook needed; redefine it so that the support test is not executed again.

			return (this.get = hookFn).apply( this, arguments );
		}
	};
}


(function() {
	var pixelPositionVal, boxSizingReliableVal,
		docElem = document.documentElement,
		container = document.createElement( "div" ),
		div = document.createElement( "div" );

	if ( !div.style ) {
		return;
	}

	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	container.style.cssText = "border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;" +
		"position:absolute";
	container.appendChild( div );

	// Executing both pixelPosition & boxSizingReliable tests require only one layout
	// so they're executed at the same time to save the second computation.
	function computePixelPositionAndBoxSizingReliable() {
		div.style.cssText =
			// Support: Firefox<29, Android 2.3
			// Vendor-prefix box-sizing
			"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" +
			"box-sizing:border-box;display:block;margin-top:1%;top:1%;" +
			"border:1px;padding:1px;width:4px;position:absolute";
		div.innerHTML = "";
		docElem.appendChild( container );

		var divStyle = window.getComputedStyle( div, null );
		pixelPositionVal = divStyle.top !== "1%";
		boxSizingReliableVal = divStyle.width === "4px";

		docElem.removeChild( container );
	}

	// Support: node.js jsdom
	// Don't assume that getComputedStyle is a property of the global object
	if ( window.getComputedStyle ) {
		jQuery.extend( support, {
			pixelPosition: function() {
				// This test is executed only once but we still do memoizing
				// since we can use the boxSizingReliable pre-computing.
				// No need to check if the test was already performed, though.
				computePixelPositionAndBoxSizingReliable();
				return pixelPositionVal;
			},
			boxSizingReliable: function() {
				if ( boxSizingReliableVal == null ) {
					computePixelPositionAndBoxSizingReliable();
				}
				return boxSizingReliableVal;
			},
			reliableMarginRight: function() {
				// Support: Android 2.3
				// Check if div with explicit width and no margin-right incorrectly
				// gets computed margin-right based on width of container. (#3333)
				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
				// This support function is only executed once so no memoizing is needed.
				var ret,
					marginDiv = div.appendChild( document.createElement( "div" ) );

				// Reset CSS: box-sizing; display; margin; border; padding
				marginDiv.style.cssText = div.style.cssText =
					// Support: Firefox<29, Android 2.3
					// Vendor-prefix box-sizing
					"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
					"box-sizing:content-box;display:block;margin:0;border:0;padding:0";
				marginDiv.style.marginRight = marginDiv.style.width = "0";
				div.style.width = "1px";
				docElem.appendChild( container );

				ret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );

				docElem.removeChild( container );

				return ret;
			}
		});
	}
})();


// A method for quickly swapping in/out CSS properties to get correct calculations.
jQuery.swap = function( elem, options, callback, args ) {
	var ret, name,
		old = {};

	// Remember the old values, and insert the new ones
	for ( name in options ) {
		old[ name ] = elem.style[ name ];
		elem.style[ name ] = options[ name ];
	}

	ret = callback.apply( elem, args || [] );

	// Revert the old values
	for ( name in options ) {
		elem.style[ name ] = old[ name ];
	}

	return ret;
};


var
	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
	rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
	rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ),

	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: "0",
		fontWeight: "400"
	},

	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];

// return a css property mapped to a potentially vendor prefixed property
function vendorPropName( style, name ) {

	// shortcut for names that are not vendor prefixed
	if ( name in style ) {
		return name;
	}

	// check for vendor prefixed names
	var capName = name[0].toUpperCase() + name.slice(1),
		origName = name,
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in style ) {
			return name;
		}
	}

	return origName;
}

function setPositiveNumber( elem, value, subtract ) {
	var matches = rnumsplit.exec( value );
	return matches ?
		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
		value;
}

function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
	var i = extra === ( isBorderBox ? "border" : "content" ) ?
		// If we already have the right measurement, avoid augmentation
		4 :
		// Otherwise initialize for horizontal or vertical properties
		name === "width" ? 1 : 0,

		val = 0;

	for ( ; i < 4; i += 2 ) {
		// both box models exclude margin, so add it if we want it
		if ( extra === "margin" ) {
			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
		}

		if ( isBorderBox ) {
			// border-box includes padding, so remove it if we want content
			if ( extra === "content" ) {
				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
			}

			// at this point, extra isn't border nor margin, so remove border
			if ( extra !== "margin" ) {
				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		} else {
			// at this point, extra isn't content, so add padding
			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

			// at this point, extra isn't content nor padding, so add border
			if ( extra !== "padding" ) {
				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		}
	}

	return val;
}

function getWidthOrHeight( elem, name, extra ) {

	// Start with offset property, which is equivalent to the border-box value
	var valueIsBorderBox = true,
		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
		styles = getStyles( elem ),
		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

	// some non-html elements return undefined for offsetWidth, so check for null/undefined
	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
	if ( val <= 0 || val == null ) {
		// Fall back to computed then uncomputed css if necessary
		val = curCSS( elem, name, styles );
		if ( val < 0 || val == null ) {
			val = elem.style[ name ];
		}

		// Computed unit is not pixels. Stop here and return.
		if ( rnumnonpx.test(val) ) {
			return val;
		}

		// we need the check for style in case a browser which returns unreliable values
		// for getComputedStyle silently falls back to the reliable elem.style
		valueIsBorderBox = isBorderBox &&
			( support.boxSizingReliable() || val === elem.style[ name ] );

		// Normalize "", auto, and prepare for extra
		val = parseFloat( val ) || 0;
	}

	// use the active box-sizing model to add/subtract irrelevant styles
	return ( val +
		augmentWidthOrHeight(
			elem,
			name,
			extra || ( isBorderBox ? "border" : "content" ),
			valueIsBorderBox,
			styles
		)
	) + "px";
}

function showHide( elements, show ) {
	var display, elem, hidden,
		values = [],
		index = 0,
		length = elements.length;

	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		values[ index ] = data_priv.get( elem, "olddisplay" );
		display = elem.style.display;
		if ( show ) {
			// Reset the inline display of this element to learn if it is
			// being hidden by cascaded rules or not
			if ( !values[ index ] && display === "none" ) {
				elem.style.display = "";
			}

			// Set elements which have been overridden with display: none
			// in a stylesheet to whatever the default browser style is
			// for such an element
			if ( elem.style.display === "" && isHidden( elem ) ) {
				values[ index ] = data_priv.access( elem, "olddisplay", defaultDisplay(elem.nodeName) );
			}
		} else {
			hidden = isHidden( elem );

			if ( display !== "none" || !hidden ) {
				data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
			}
		}
	}

	// Set the display of most of the elements in a second loop
	// to avoid the constant reflow
	for ( index = 0; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}
		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
			elem.style.display = show ? values[ index ] || "" : "none";
		}
	}

	return elements;
}

jQuery.extend({
	// Add in style property hooks for overriding the default
	// behavior of getting and setting a style property
	cssHooks: {
		opacity: {
			get: function( elem, computed ) {
				if ( computed ) {
					// We should always get a number back from opacity
					var ret = curCSS( elem, "opacity" );
					return ret === "" ? "1" : ret;
				}
			}
		}
	},

	// Don't automatically add "px" to these possibly-unitless properties
	cssNumber: {
		"columnCount": true,
		"fillOpacity": true,
		"flexGrow": true,
		"flexShrink": true,
		"fontWeight": true,
		"lineHeight": true,
		"opacity": true,
		"order": true,
		"orphans": true,
		"widows": true,
		"zIndex": true,
		"zoom": true
	},

	// Add in properties whose names you wish to fix before
	// setting or getting the value
	cssProps: {
		// normalize float css property
		"float": "cssFloat"
	},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {
		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = jQuery.camelCase( name ),
			style = elem.style;

		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// Check if we're setting a value
		if ( value !== undefined ) {
			type = typeof value;

			// convert relative number strings (+= or -=) to relative numbers. #7345
			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
				// Fixes bug #9237
				type = "number";
			}

			// Make sure that null and NaN values aren't set. See: #7116
			if ( value == null || value !== value ) {
				return;
			}

			// If a number was passed in, add 'px' to the (except for certain CSS properties)
			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
				value += "px";
			}

			// Fixes #8908, it can be done more correctly by specifying setters in cssHooks,
			// but it would mean to define eight (for every problematic property) identical functions
			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
				style[ name ] = "inherit";
			}

			// If a hook was provided, use that value, otherwise just set the specified value
			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
				style[ name ] = value;
			}

		} else {
			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var val, num, hooks,
			origName = jQuery.camelCase( name );

		// Make sure that we're working with the right name
		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// If a hook was provided get the computed value from there
		if ( hooks && "get" in hooks ) {
			val = hooks.get( elem, true, extra );
		}

		// Otherwise, if a way to get the computed value exists, use that
		if ( val === undefined ) {
			val = curCSS( elem, name, styles );
		}

		//convert "normal" to computed value
		if ( val === "normal" && name in cssNormalTransform ) {
			val = cssNormalTransform[ name ];
		}

		// Return, converting to number if forced or a qualifier was provided and val looks numeric
		if ( extra === "" || extra ) {
			num = parseFloat( val );
			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
		}
		return val;
	}
});

jQuery.each([ "height", "width" ], function( i, name ) {
	jQuery.cssHooks[ name ] = {
		get: function( elem, computed, extra ) {
			if ( computed ) {
				// certain elements can have dimension info if we invisibly show them
				// however, it must have a current display style that would benefit from this
				return rdisplayswap.test( jQuery.css( elem, "display" ) ) && elem.offsetWidth === 0 ?
					jQuery.swap( elem, cssShow, function() {
						return getWidthOrHeight( elem, name, extra );
					}) :
					getWidthOrHeight( elem, name, extra );
			}
		},

		set: function( elem, value, extra ) {
			var styles = extra && getStyles( elem );
			return setPositiveNumber( elem, value, extra ?
				augmentWidthOrHeight(
					elem,
					name,
					extra,
					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
					styles
				) : 0
			);
		}
	};
});

// Support: Android 2.3
jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
	function( elem, computed ) {
		if ( computed ) {
			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
			// Work around by temporarily setting element display to inline-block
			return jQuery.swap( elem, { "display": "inline-block" },
				curCSS, [ elem, "marginRight" ] );
		}
	}
);

// These hooks are used by animate to expand properties
jQuery.each({
	margin: "",
	padding: "",
	border: "Width"
}, function( prefix, suffix ) {
	jQuery.cssHooks[ prefix + suffix ] = {
		expand: function( value ) {
			var i = 0,
				expanded = {},

				// assumes a single number if not a string
				parts = typeof value === "string" ? value.split(" ") : [ value ];

			for ( ; i < 4; i++ ) {
				expanded[ prefix + cssExpand[ i ] + suffix ] =
					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
			}

			return expanded;
		}
	};

	if ( !rmargin.test( prefix ) ) {
		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
	}
});

jQuery.fn.extend({
	css: function( name, value ) {
		return access( this, function( elem, name, value ) {
			var styles, len,
				map = {},
				i = 0;

			if ( jQuery.isArray( name ) ) {
				styles = getStyles( elem );
				len = name.length;

				for ( ; i < len; i++ ) {
					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
				}

				return map;
			}

			return value !== undefined ?
				jQuery.style( elem, name, value ) :
				jQuery.css( elem, name );
		}, name, value, arguments.length > 1 );
	},
	show: function() {
		return showHide( this, true );
	},
	hide: function() {
		return showHide( this );
	},
	toggle: function( state ) {
		if ( typeof state === "boolean" ) {
			return state ? this.show() : this.hide();
		}

		return this.each(function() {
			if ( isHidden( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		});
	}
});


function Tween( elem, options, prop, end, easing ) {
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || "swing";
		this.options = options;
		this.start = this.now = this.cur();
		this.end = end;
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
	},
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];

		return hooks && hooks.get ?
			hooks.get( this ) :
			Tween.propHooks._default.get( this );
	},
	run: function( percent ) {
		var eased,
			hooks = Tween.propHooks[ this.prop ];

		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
		} else {
			this.pos = eased = percent;
		}
		this.now = ( this.end - this.start ) * eased + this.start;

		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}

		if ( hooks && hooks.set ) {
			hooks.set( this );
		} else {
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			if ( tween.elem[ tween.prop ] != null &&
				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
				return tween.elem[ tween.prop ];
			}

			// passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails
			// so, simple values such as "10px" are parsed to Float.
			// complex values such as "rotate(1rad)" are returned as is.
			result = jQuery.css( tween.elem, tween.prop, "" );
			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {
			// use step hook for back compat - use cssHook if its there - use .style if its
			// available and use plain properties where available
			if ( jQuery.fx.step[ tween.prop ] ) {
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
			} else {
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

// Support: IE9
// Panic based approach to setting things on disconnected nodes

Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
	set: function( tween ) {
		if ( tween.elem.nodeType && tween.elem.parentNode ) {
			tween.elem[ tween.prop ] = tween.now;
		}
	}
};

jQuery.easing = {
	linear: function( p ) {
		return p;
	},
	swing: function( p ) {
		return 0.5 - Math.cos( p * Math.PI ) / 2;
	}
};

jQuery.fx = Tween.prototype.init;

// Back Compat <1.8 extension point
jQuery.fx.step = {};




var
	fxNow, timerId,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
	rrun = /queueHooks$/,
	animationPrefilters = [ defaultPrefilter ],
	tweeners = {
		"*": [ function( prop, value ) {
			var tween = this.createTween( prop, value ),
				target = tween.cur(),
				parts = rfxnum.exec( value ),
				unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),

				// Starting value computation is required for potential unit mismatches
				start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
					rfxnum.exec( jQuery.css( tween.elem, prop ) ),
				scale = 1,
				maxIterations = 20;

			if ( start && start[ 3 ] !== unit ) {
				// Trust units reported by jQuery.css
				unit = unit || start[ 3 ];

				// Make sure we update the tween properties later on
				parts = parts || [];

				// Iteratively approximate from a nonzero starting point
				start = +target || 1;

				do {
					// If previous iteration zeroed out, double until we get *something*
					// Use a string for doubling factor so we don't accidentally see scale as unchanged below
					scale = scale || ".5";

					// Adjust and apply
					start = start / scale;
					jQuery.style( tween.elem, prop, start + unit );

				// Update scale, tolerating zero or NaN from tween.cur()
				// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
				} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
			}

			// Update tween properties
			if ( parts ) {
				start = tween.start = +start || +target || 0;
				tween.unit = unit;
				// If a +=/-= token was provided, we're doing a relative animation
				tween.end = parts[ 1 ] ?
					start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
					+parts[ 2 ];
			}

			return tween;
		} ]
	};

// Animations created synchronously will run synchronously
function createFxNow() {
	setTimeout(function() {
		fxNow = undefined;
	});
	return ( fxNow = jQuery.now() );
}

// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
	var which,
		i = 0,
		attrs = { height: type };

	// if we include width, step value is 1 to do all cssExpand values,
	// if we don't include width, step value is 2 to skip over Left and Right
	includeWidth = includeWidth ? 1 : 0;
	for ( ; i < 4 ; i += 2 - includeWidth ) {
		which = cssExpand[ i ];
		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
	}

	if ( includeWidth ) {
		attrs.opacity = attrs.width = type;
	}

	return attrs;
}

function createTween( value, prop, animation ) {
	var tween,
		collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
		index = 0,
		length = collection.length;
	for ( ; index < length; index++ ) {
		if ( (tween = collection[ index ].call( animation, prop, value )) ) {

			// we're done with this property
			return tween;
		}
	}
}

function defaultPrefilter( elem, props, opts ) {
	/* jshint validthis: true */
	var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHidden( elem ),
		dataShow = data_priv.get( elem, "fxshow" );

	// handle queue: false promises
	if ( !opts.queue ) {
		hooks = jQuery._queueHooks( elem, "fx" );
		if ( hooks.unqueued == null ) {
			hooks.unqueued = 0;
			oldfire = hooks.empty.fire;
			hooks.empty.fire = function() {
				if ( !hooks.unqueued ) {
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always(function() {
			// doing this makes sure that the complete handler will be called
			// before this completes
			anim.always(function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			});
		});
	}

	// height/width overflow pass
	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
		// Make sure that nothing sneaks out
		// Record all 3 overflow attributes because IE9-10 do not
		// change the overflow attribute when overflowX and
		// overflowY are set to the same value
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Set display property to inline-block for height/width
		// animations on inline elements that are having width/height animated
		display = jQuery.css( elem, "display" );

		// Test default display if display is currently "none"
		checkDisplay = display === "none" ?
			data_priv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;

		if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
			style.display = "inline-block";
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		anim.always(function() {
			style.overflow = opts.overflow[ 0 ];
			style.overflowX = opts.overflow[ 1 ];
			style.overflowY = opts.overflow[ 2 ];
		});
	}

	// show/hide pass
	for ( prop in props ) {
		value = props[ prop ];
		if ( rfxtypes.exec( value ) ) {
			delete props[ prop ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {

				// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
					hidden = true;
				} else {
					continue;
				}
			}
			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );

		// Any non-fx value stops us from restoring the original display value
		} else {
			display = undefined;
		}
	}

	if ( !jQuery.isEmptyObject( orig ) ) {
		if ( dataShow ) {
			if ( "hidden" in dataShow ) {
				hidden = dataShow.hidden;
			}
		} else {
			dataShow = data_priv.access( elem, "fxshow", {} );
		}

		// store state if its toggle - enables .stop().toggle() to "reverse"
		if ( toggle ) {
			dataShow.hidden = !hidden;
		}
		if ( hidden ) {
			jQuery( elem ).show();
		} else {
			anim.done(function() {
				jQuery( elem ).hide();
			});
		}
		anim.done(function() {
			var prop;

			data_priv.remove( elem, "fxshow" );
			for ( prop in orig ) {
				jQuery.style( elem, prop, orig[ prop ] );
			}
		});
		for ( prop in orig ) {
			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );

			if ( !( prop in dataShow ) ) {
				dataShow[ prop ] = tween.start;
				if ( hidden ) {
					tween.end = tween.start;
					tween.start = prop === "width" || prop === "height" ? 1 : 0;
				}
			}
		}

	// If this is a noop like .hide().hide(), restore an overwritten display value
	} else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) {
		style.display = display;
	}
}

function propFilter( props, specialEasing ) {
	var index, name, easing, value, hooks;

	// camelCase, specialEasing and expand cssHook pass
	for ( index in props ) {
		name = jQuery.camelCase( index );
		easing = specialEasing[ name ];
		value = props[ index ];
		if ( jQuery.isArray( value ) ) {
			easing = value[ 1 ];
			value = props[ index ] = value[ 0 ];
		}

		if ( index !== name ) {
			props[ name ] = value;
			delete props[ index ];
		}

		hooks = jQuery.cssHooks[ name ];
		if ( hooks && "expand" in hooks ) {
			value = hooks.expand( value );
			delete props[ name ];

			// not quite $.extend, this wont overwrite keys already present.
			// also - reusing 'index' from above because we have the correct "name"
			for ( index in value ) {
				if ( !( index in props ) ) {
					props[ index ] = value[ index ];
					specialEasing[ index ] = easing;
				}
			}
		} else {
			specialEasing[ name ] = easing;
		}
	}
}

function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = animationPrefilters.length,
		deferred = jQuery.Deferred().always( function() {
			// don't match elem in the :animated selector
			delete tick.elem;
		}),
		tick = function() {
			if ( stopped ) {
				return false;
			}
			var currentTime = fxNow || createFxNow(),
				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
				temp = remaining / animation.duration || 0,
				percent = 1 - temp,
				index = 0,
				length = animation.tweens.length;

			for ( ; index < length ; index++ ) {
				animation.tweens[ index ].run( percent );
			}

			deferred.notifyWith( elem, [ animation, percent, remaining ]);

			if ( percent < 1 && length ) {
				return remaining;
			} else {
				deferred.resolveWith( elem, [ animation ] );
				return false;
			}
		},
		animation = deferred.promise({
			elem: elem,
			props: jQuery.extend( {}, properties ),
			opts: jQuery.extend( true, { specialEasing: {} }, options ),
			originalProperties: properties,
			originalOptions: options,
			startTime: fxNow || createFxNow(),
			duration: options.duration,
			tweens: [],
			createTween: function( prop, end ) {
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
						animation.opts.specialEasing[ prop ] || animation.opts.easing );
				animation.tweens.push( tween );
				return tween;
			},
			stop: function( gotoEnd ) {
				var index = 0,
					// if we are going to the end, we want to run all the tweens
					// otherwise we skip this part
					length = gotoEnd ? animation.tweens.length : 0;
				if ( stopped ) {
					return this;
				}
				stopped = true;
				for ( ; index < length ; index++ ) {
					animation.tweens[ index ].run( 1 );
				}

				// resolve when we played the last frame
				// otherwise, reject
				if ( gotoEnd ) {
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		}),
		props = animation.props;

	propFilter( props, animation.opts.specialEasing );

	for ( ; index < length ; index++ ) {
		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
		if ( result ) {
			return result;
		}
	}

	jQuery.map( props, createTween, animation );

	if ( jQuery.isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		})
	);

	// attach callbacks from options
	return animation.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );
}

jQuery.Animation = jQuery.extend( Animation, {

	tweener: function( props, callback ) {
		if ( jQuery.isFunction( props ) ) {
			callback = props;
			props = [ "*" ];
		} else {
			props = props.split(" ");
		}

		var prop,
			index = 0,
			length = props.length;

		for ( ; index < length ; index++ ) {
			prop = props[ index ];
			tweeners[ prop ] = tweeners[ prop ] || [];
			tweeners[ prop ].unshift( callback );
		}
	},

	prefilter: function( callback, prepend ) {
		if ( prepend ) {
			animationPrefilters.unshift( callback );
		} else {
			animationPrefilters.push( callback );
		}
	}
});

jQuery.speed = function( speed, easing, fn ) {
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		complete: fn || !fn && easing ||
			jQuery.isFunction( speed ) && speed,
		duration: speed,
		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
	};

	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;

	// normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		if ( jQuery.isFunction( opt.old ) ) {
			opt.old.call( this );
		}

		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};

	return opt;
};

jQuery.fn.extend({
	fadeTo: function( speed, to, easing, callback ) {

		// show any hidden elements after setting opacity to 0
		return this.filter( isHidden ).css( "opacity", 0 ).show()

			// animate to the value specified
			.end().animate({ opacity: to }, speed, easing, callback );
	},
	animate: function( prop, speed, easing, callback ) {
		var empty = jQuery.isEmptyObject( prop ),
			optall = jQuery.speed( speed, easing, callback ),
			doAnimation = function() {
				// Operate on a copy of prop so per-property easing won't be lost
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );

				// Empty animations, or finishing resolves immediately
				if ( empty || data_priv.get( this, "finish" ) ) {
					anim.stop( true );
				}
			};
			doAnimation.finish = doAnimation;

		return empty || optall.queue === false ?
			this.each( doAnimation ) :
			this.queue( optall.queue, doAnimation );
	},
	stop: function( type, clearQueue, gotoEnd ) {
		var stopQueue = function( hooks ) {
			var stop = hooks.stop;
			delete hooks.stop;
			stop( gotoEnd );
		};

		if ( typeof type !== "string" ) {
			gotoEnd = clearQueue;
			clearQueue = type;
			type = undefined;
		}
		if ( clearQueue && type !== false ) {
			this.queue( type || "fx", [] );
		}

		return this.each(function() {
			var dequeue = true,
				index = type != null && type + "queueHooks",
				timers = jQuery.timers,
				data = data_priv.get( this );

			if ( index ) {
				if ( data[ index ] && data[ index ].stop ) {
					stopQueue( data[ index ] );
				}
			} else {
				for ( index in data ) {
					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
						stopQueue( data[ index ] );
					}
				}
			}

			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
					timers[ index ].anim.stop( gotoEnd );
					dequeue = false;
					timers.splice( index, 1 );
				}
			}

			// start the next in the queue if the last step wasn't forced
			// timers currently will call their complete callbacks, which will dequeue
			// but only if they were gotoEnd
			if ( dequeue || !gotoEnd ) {
				jQuery.dequeue( this, type );
			}
		});
	},
	finish: function( type ) {
		if ( type !== false ) {
			type = type || "fx";
		}
		return this.each(function() {
			var index,
				data = data_priv.get( this ),
				queue = data[ type + "queue" ],
				hooks = data[ type + "queueHooks" ],
				timers = jQuery.timers,
				length = queue ? queue.length : 0;

			// enable finishing flag on private data
			data.finish = true;

			// empty the queue first
			jQuery.queue( this, type, [] );

			if ( hooks && hooks.stop ) {
				hooks.stop.call( this, true );
			}

			// look for any active animations, and finish them
			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
					timers[ index ].anim.stop( true );
					timers.splice( index, 1 );
				}
			}

			// look for any animations in the old queue and finish them
			for ( index = 0; index < length; index++ ) {
				if ( queue[ index ] && queue[ index ].finish ) {
					queue[ index ].finish.call( this );
				}
			}

			// turn off finishing flag
			delete data.finish;
		});
	}
});

jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
	var cssFn = jQuery.fn[ name ];
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return speed == null || typeof speed === "boolean" ?
			cssFn.apply( this, arguments ) :
			this.animate( genFx( name, true ), speed, easing, callback );
	};
});

// Generate shortcuts for custom animations
jQuery.each({
	slideDown: genFx("show"),
	slideUp: genFx("hide"),
	slideToggle: genFx("toggle"),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" },
	fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return this.animate( props, speed, easing, callback );
	};
});

jQuery.timers = [];
jQuery.fx.tick = function() {
	var timer,
		i = 0,
		timers = jQuery.timers;

	fxNow = jQuery.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];
		// Checks the timer has not already been removed
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	jQuery.timers.push( timer );
	if ( timer() ) {
		jQuery.fx.start();
	} else {
		jQuery.timers.pop();
	}
};

jQuery.fx.interval = 13;

jQuery.fx.start = function() {
	if ( !timerId ) {
		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
	}
};

jQuery.fx.stop = function() {
	clearInterval( timerId );
	timerId = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,
	// Default speed
	_default: 400
};


// Based off of the plugin by Clint Helfers, with permission.
// http://blindsignals.com/index.php/2009/07/jquery-delay/
jQuery.fn.delay = function( time, type ) {
	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
	type = type || "fx";

	return this.queue( type, function( next, hooks ) {
		var timeout = setTimeout( next, time );
		hooks.stop = function() {
			clearTimeout( timeout );
		};
	});
};


(function() {
	var input = document.createElement( "input" ),
		select = document.createElement( "select" ),
		opt = select.appendChild( document.createElement( "option" ) );

	input.type = "checkbox";

	// Support: iOS 5.1, Android 4.x, Android 2.3
	// Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere)
	support.checkOn = input.value !== "";

	// Must access the parent to make an option select properly
	// Support: IE9, IE10
	support.optSelected = opt.selected;

	// Make sure that the options inside disabled selects aren't marked as disabled
	// (WebKit marks them as disabled)
	select.disabled = true;
	support.optDisabled = !opt.disabled;

	// Check if an input maintains its value after becoming a radio
	// Support: IE9, IE10
	input = document.createElement( "input" );
	input.value = "t";
	input.type = "radio";
	support.radioValue = input.value === "t";
})();


var nodeHook, boolHook,
	attrHandle = jQuery.expr.attrHandle;

jQuery.fn.extend({
	attr: function( name, value ) {
		return access( this, jQuery.attr, name, value, arguments.length > 1 );
	},

	removeAttr: function( name ) {
		return this.each(function() {
			jQuery.removeAttr( this, name );
		});
	}
});

jQuery.extend({
	attr: function( elem, name, value ) {
		var hooks, ret,
			nType = elem.nodeType;

		// don't get/set attributes on text, comment and attribute nodes
		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		// Fallback to prop when attributes are not supported
		if ( typeof elem.getAttribute === strundefined ) {
			return jQuery.prop( elem, name, value );
		}

		// All attributes are lowercase
		// Grab necessary hook if one is defined
		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
			name = name.toLowerCase();
			hooks = jQuery.attrHooks[ name ] ||
				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
		}

		if ( value !== undefined ) {

			if ( value === null ) {
				jQuery.removeAttr( elem, name );

			} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
				return ret;

			} else {
				elem.setAttribute( name, value + "" );
				return value;
			}

		} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
			return ret;

		} else {
			ret = jQuery.find.attr( elem, name );

			// Non-existent attributes return null, we normalize to undefined
			return ret == null ?
				undefined :
				ret;
		}
	},

	removeAttr: function( elem, value ) {
		var name, propName,
			i = 0,
			attrNames = value && value.match( rnotwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( (name = attrNames[i++]) ) {
				propName = jQuery.propFix[ name ] || name;

				// Boolean attributes get special treatment (#10870)
				if ( jQuery.expr.match.bool.test( name ) ) {
					// Set corresponding property to false
					elem[ propName ] = false;
				}

				elem.removeAttribute( name );
			}
		}
	},

	attrHooks: {
		type: {
			set: function( elem, value ) {
				if ( !support.radioValue && value === "radio" &&
					jQuery.nodeName( elem, "input" ) ) {
					// Setting the type on a radio button after the value resets the value in IE6-9
					// Reset value to default in case type is set after value during creation
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	}
});

// Hooks for boolean attributes
boolHook = {
	set: function( elem, value, name ) {
		if ( value === false ) {
			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else {
			elem.setAttribute( name, name );
		}
		return name;
	}
};
jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
	var getter = attrHandle[ name ] || jQuery.find.attr;

	attrHandle[ name ] = function( elem, name, isXML ) {
		var ret, handle;
		if ( !isXML ) {
			// Avoid an infinite loop by temporarily removing this function from the getter
			handle = attrHandle[ name ];
			attrHandle[ name ] = ret;
			ret = getter( elem, name, isXML ) != null ?
				name.toLowerCase() :
				null;
			attrHandle[ name ] = handle;
		}
		return ret;
	};
});




var rfocusable = /^(?:input|select|textarea|button)$/i;

jQuery.fn.extend({
	prop: function( name, value ) {
		return access( this, jQuery.prop, name, value, arguments.length > 1 );
	},

	removeProp: function( name ) {
		return this.each(function() {
			delete this[ jQuery.propFix[ name ] || name ];
		});
	}
});

jQuery.extend({
	propFix: {
		"for": "htmlFor",
		"class": "className"
	},

	prop: function( elem, name, value ) {
		var ret, hooks, notxml,
			nType = elem.nodeType;

		// don't get/set properties on text, comment and attribute nodes
		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

		if ( notxml ) {
			// Fix name and attach hooks
			name = jQuery.propFix[ name ] || name;
			hooks = jQuery.propHooks[ name ];
		}

		if ( value !== undefined ) {
			return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
				ret :
				( elem[ name ] = value );

		} else {
			return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
				ret :
				elem[ name ];
		}
	},

	propHooks: {
		tabIndex: {
			get: function( elem ) {
				return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
					elem.tabIndex :
					-1;
			}
		}
	}
});

// Support: IE9+
// Selectedness for an option in an optgroup can be inaccurate
if ( !support.optSelected ) {
	jQuery.propHooks.selected = {
		get: function( elem ) {
			var parent = elem.parentNode;
			if ( parent && parent.parentNode ) {
				parent.parentNode.selectedIndex;
			}
			return null;
		}
	};
}

jQuery.each([
	"tabIndex",
	"readOnly",
	"maxLength",
	"cellSpacing",
	"cellPadding",
	"rowSpan",
	"colSpan",
	"useMap",
	"frameBorder",
	"contentEditable"
], function() {
	jQuery.propFix[ this.toLowerCase() ] = this;
});




var rclass = /[\t\r\n\f]/g;

jQuery.fn.extend({
	addClass: function( value ) {
		var classes, elem, cur, clazz, j, finalValue,
			proceed = typeof value === "string" && value,
			i = 0,
			len = this.length;

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).addClass( value.call( this, j, this.className ) );
			});
		}

		if ( proceed ) {
			// The disjunction here is for better compressibility (see removeClass)
			classes = ( value || "" ).match( rnotwhite ) || [];

			for ( ; i < len; i++ ) {
				elem = this[ i ];
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					" "
				);

				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}

					// only assign if different to avoid unneeded rendering.
					finalValue = jQuery.trim( cur );
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
				}
			}
		}

		return this;
	},

	removeClass: function( value ) {
		var classes, elem, cur, clazz, j, finalValue,
			proceed = arguments.length === 0 || typeof value === "string" && value,
			i = 0,
			len = this.length;

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).removeClass( value.call( this, j, this.className ) );
			});
		}
		if ( proceed ) {
			classes = ( value || "" ).match( rnotwhite ) || [];

			for ( ; i < len; i++ ) {
				elem = this[ i ];
				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					""
				);

				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}

					// only assign if different to avoid unneeded rendering.
					finalValue = value ? jQuery.trim( cur ) : "";
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
				}
			}
		}

		return this;
	},

	toggleClass: function( value, stateVal ) {
		var type = typeof value;

		if ( typeof stateVal === "boolean" && type === "string" ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( i ) {
				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
			});
		}

		return this.each(function() {
			if ( type === "string" ) {
				// toggle individual class names
				var className,
					i = 0,
					self = jQuery( this ),
					classNames = value.match( rnotwhite ) || [];

				while ( (className = classNames[ i++ ]) ) {
					// check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}

			// Toggle whole class name
			} else if ( type === strundefined || type === "boolean" ) {
				if ( this.className ) {
					// store className if set
					data_priv.set( this, "__className__", this.className );
				}

				// If the element has a class name or if we're passed "false",
				// then remove the whole classname (if there was one, the above saved it).
				// Otherwise bring back whatever was previously saved (if anything),
				// falling back to the empty string if nothing was stored.
				this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
			}
		});
	},

	hasClass: function( selector ) {
		var className = " " + selector + " ",
			i = 0,
			l = this.length;
		for ( ; i < l; i++ ) {
			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
				return true;
			}
		}

		return false;
	}
});




var rreturn = /\r/g;

jQuery.fn.extend({
	val: function( value ) {
		var hooks, ret, isFunction,
			elem = this[0];

		if ( !arguments.length ) {
			if ( elem ) {
				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];

				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
					return ret;
				}

				ret = elem.value;

				return typeof ret === "string" ?
					// handle most common string cases
					ret.replace(rreturn, "") :
					// handle cases where value is null/undef or number
					ret == null ? "" : ret;
			}

			return;
		}

		isFunction = jQuery.isFunction( value );

		return this.each(function( i ) {
			var val;

			if ( this.nodeType !== 1 ) {
				return;
			}

			if ( isFunction ) {
				val = value.call( this, i, jQuery( this ).val() );
			} else {
				val = value;
			}

			// Treat null/undefined as ""; convert numbers to string
			if ( val == null ) {
				val = "";

			} else if ( typeof val === "number" ) {
				val += "";

			} else if ( jQuery.isArray( val ) ) {
				val = jQuery.map( val, function( value ) {
					return value == null ? "" : value + "";
				});
			}

			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

			// If set returns undefined, fall back to normal setting
			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
				this.value = val;
			}
		});
	}
});

jQuery.extend({
	valHooks: {
		option: {
			get: function( elem ) {
				var val = jQuery.find.attr( elem, "value" );
				return val != null ?
					val :
					// Support: IE10-11+
					// option.text throws exceptions (#14686, #14858)
					jQuery.trim( jQuery.text( elem ) );
			}
		},
		select: {
			get: function( elem ) {
				var value, option,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one" || index < 0,
					values = one ? null : [],
					max = one ? index + 1 : options.length,
					i = index < 0 ?
						max :
						one ? index : 0;

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// IE6-9 doesn't update selected after form reset (#2551)
					if ( ( option.selected || i === index ) &&
							// Don't return options that are disabled or in a disabled optgroup
							( support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) &&
							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var optionSet, option,
					options = elem.options,
					values = jQuery.makeArray( value ),
					i = options.length;

				while ( i-- ) {
					option = options[ i ];
					if ( (option.selected = jQuery.inArray( option.value, values ) >= 0) ) {
						optionSet = true;
					}
				}

				// force browsers to behave consistently when non-matching value is set
				if ( !optionSet ) {
					elem.selectedIndex = -1;
				}
				return values;
			}
		}
	}
});

// Radios and checkboxes getter/setter
jQuery.each([ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = {
		set: function( elem, value ) {
			if ( jQuery.isArray( value ) ) {
				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
			}
		}
	};
	if ( !support.checkOn ) {
		jQuery.valHooks[ this ].get = function( elem ) {
			// Support: Webkit
			// "" is returned instead of "on" if a value isn't specified
			return elem.getAttribute("value") === null ? "on" : elem.value;
		};
	}
});




// Return jQuery for attributes-only inclusion


jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};
});

jQuery.fn.extend({
	hover: function( fnOver, fnOut ) {
		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
	},

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {
		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
	}
});


var nonce = jQuery.now();

var rquery = (/\?/);



// Support: Android 2.3
// Workaround failure to string-cast null input
jQuery.parseJSON = function( data ) {
	return JSON.parse( data + "" );
};


// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
	var xml, tmp;
	if ( !data || typeof data !== "string" ) {
		return null;
	}

	// Support: IE9
	try {
		tmp = new DOMParser();
		xml = tmp.parseFromString( data, "text/xml" );
	} catch ( e ) {
		xml = undefined;
	}

	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
		jQuery.error( "Invalid XML: " + data );
	}
	return xml;
};


var
	// Document location
	ajaxLocParts,
	ajaxLocation,

	rhash = /#.*$/,
	rts = /([?&])_=[^&]*/,
	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
	// #7653, #8125, #8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,
	rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,

	/* Prefilters
	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
	 * 2) These are called:
	 *    - BEFORE asking for a transport
	 *    - AFTER param serialization (s.data is a string if s.processData is true)
	 * 3) key is the dataType
	 * 4) the catchall symbol "*" can be used
	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
	 */
	prefilters = {},

	/* Transports bindings
	 * 1) key is the dataType
	 * 2) the catchall symbol "*" can be used
	 * 3) selection will start with transport dataType and THEN go to "*" if needed
	 */
	transports = {},

	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
	allTypes = "*/".concat("*");

// #8138, IE may throw an exception when accessing
// a field from window.location if document.domain has been set
try {
	ajaxLocation = location.href;
} catch( e ) {
	// Use the href attribute of an A element
	// since IE will modify it given document.location
	ajaxLocation = document.createElement( "a" );
	ajaxLocation.href = "";
	ajaxLocation = ajaxLocation.href;
}

// Segment location into parts
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];

		if ( jQuery.isFunction( func ) ) {
			// For each dataType in the dataTypeExpression
			while ( (dataType = dataTypes[i++]) ) {
				// Prepend if requested
				if ( dataType[0] === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );

				// Otherwise append
				} else {
					(structure[ dataType ] = structure[ dataType ] || []).push( func );
				}
			}
		}
	};
}

// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );

	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		});
		return selected;
	}

	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
function ajaxExtend( target, src ) {
	var key, deep,
		flatOptions = jQuery.ajaxSettings.flatOptions || {};

	for ( key in src ) {
		if ( src[ key ] !== undefined ) {
			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
		}
	}
	if ( deep ) {
		jQuery.extend( true, target, deep );
	}

	return target;
}

/* Handles responses to an ajax request:
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
function ajaxHandleResponses( s, jqXHR, responses ) {

	var ct, type, finalDataType, firstDataType,
		contents = s.contents,
		dataTypes = s.dataTypes;

	// Remove auto dataType and get content-type in the process
	while ( dataTypes[ 0 ] === "*" ) {
		dataTypes.shift();
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
		}
	}

	// Check if we're dealing with a known content-type
	if ( ct ) {
		for ( type in contents ) {
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );
				break;
			}
		}
	}

	// Check to see if we have a response for the expected dataType
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {
		// Try convertible dataTypes
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
				finalDataType = type;
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}
		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}

	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );
		}
		return responses[ finalDataType ];
	}
}

/* Chain conversions given the request and the original response
 * Also sets the responseXXX fields on the jqXHR instance
 */
function ajaxConvert( s, response, jqXHR, isSuccess ) {
	var conv2, current, conv, tmp, prev,
		converters = {},
		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice();

	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}

	current = dataTypes.shift();

	// Convert to each sequential dataType
	while ( current ) {

		if ( s.responseFields[ current ] ) {
			jqXHR[ s.responseFields[ current ] ] = response;
		}

		// Apply the dataFilter if provided
		if ( !prev && isSuccess && s.dataFilter ) {
			response = s.dataFilter( response, s.dataType );
		}

		prev = current;
		current = dataTypes.shift();

		if ( current ) {

		// There's only work to do if current dataType is non-auto
			if ( current === "*" ) {

				current = prev;

			// Convert response if prev dataType is non-auto and differs from current
			} else if ( prev !== "*" && prev !== current ) {

				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];

				// If none found, seek a pair
				if ( !conv ) {
					for ( conv2 in converters ) {

						// If conv2 outputs current
						tmp = conv2.split( " " );
						if ( tmp[ 1 ] === current ) {

							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							if ( conv ) {
								// Condense equivalence converters
								if ( conv === true ) {
									conv = converters[ conv2 ];

								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									current = tmp[ 0 ];
									dataTypes.unshift( tmp[ 1 ] );
								}
								break;
							}
						}
					}
				}

				// Apply converter (if not an equivalence)
				if ( conv !== true ) {

					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s[ "throws" ] ) {
						response = conv( response );
					} else {
						try {
							response = conv( response );
						} catch ( e ) {
							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
						}
					}
				}
			}
		}
	}

	return { state: "success", data: response };
}

jQuery.extend({

	// Counter for holding the number of active queries
	active: 0,

	// Last-Modified header cache for next request
	lastModified: {},
	etag: {},

	ajaxSettings: {
		url: ajaxLocation,
		type: "GET",
		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
		global: true,
		processData: true,
		async: true,
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
		/*
		timeout: 0,
		data: null,
		dataType: null,
		username: null,
		password: null,
		cache: null,
		throws: false,
		traditional: false,
		headers: {},
		*/

		accepts: {
			"*": allTypes,
			text: "text/plain",
			html: "text/html",
			xml: "application/xml, text/xml",
			json: "application/json, text/javascript"
		},

		contents: {
			xml: /xml/,
			html: /html/,
			json: /json/
		},

		responseFields: {
			xml: "responseXML",
			text: "responseText",
			json: "responseJSON"
		},

		// Data converters
		// Keys separate source (or catchall "*") and destination types with a single space
		converters: {

			// Convert anything to text
			"* text": String,

			// Text to html (true = no transformation)
			"text html": true,

			// Evaluate text as a json expression
			"text json": jQuery.parseJSON,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			url: true,
			context: true
		}
	},

	// Creates a full fledged settings object into target
	// with both ajaxSettings and settings fields.
	// If target is omitted, writes into ajaxSettings.
	ajaxSetup: function( target, settings ) {
		return settings ?

			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	},

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
	ajaxTransport: addToPrefiltersOrTransports( transports ),

	// Main method
	ajax: function( url, options ) {

		// If url is an object, simulate pre-1.5 signature
		if ( typeof url === "object" ) {
			options = url;
			url = undefined;
		}

		// Force options to be an object
		options = options || {};

		var transport,
			// URL without anti-cache param
			cacheURL,
			// Response headers
			responseHeadersString,
			responseHeaders,
			// timeout handle
			timeoutTimer,
			// Cross-domain detection vars
			parts,
			// To know if global events are to be dispatched
			fireGlobals,
			// Loop variable
			i,
			// Create the final options object
			s = jQuery.ajaxSetup( {}, options ),
			// Callbacks context
			callbackContext = s.context || s,
			// Context for global events is callbackContext if it is a DOM node or jQuery collection
			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
				jQuery( callbackContext ) :
				jQuery.event,
			// Deferreds
			deferred = jQuery.Deferred(),
			completeDeferred = jQuery.Callbacks("once memory"),
			// Status-dependent callbacks
			statusCode = s.statusCode || {},
			// Headers (they are sent all at once)
			requestHeaders = {},
			requestHeadersNames = {},
			// The jqXHR state
			state = 0,
			// Default abort message
			strAbort = "canceled",
			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( state === 2 ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( (match = rheaders.exec( responseHeadersString )) ) {
								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
							}
						}
						match = responseHeaders[ key.toLowerCase() ];
					}
					return match == null ? null : match;
				},

				// Raw string
				getAllResponseHeaders: function() {
					return state === 2 ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					var lname = name.toLowerCase();
					if ( !state ) {
						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( !state ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( state < 2 ) {
							for ( code in map ) {
								// Lazy-add the new callback in a way that preserves old ones
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						} else {
							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						}
					}
					return this;
				},

				// Cancel the request
				abort: function( statusText ) {
					var finalText = statusText || strAbort;
					if ( transport ) {
						transport.abort( finalText );
					}
					done( 0, finalText );
					return this;
				}
			};

		// Attach deferreds
		deferred.promise( jqXHR ).complete = completeDeferred.add;
		jqXHR.success = jqXHR.done;
		jqXHR.error = jqXHR.fail;

		// Remove hash character (#7531: and string promotion)
		// Add protocol if not provided (prefilters might expect it)
		// Handle falsy url in the settings object (#10093: consistency with old signature)
		// We also use the url parameter if available
		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
			.replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

		// Alias method option to type as per ticket #12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];

		// A cross-domain request is in order when we have a protocol:host:port mismatch
		if ( s.crossDomain == null ) {
			parts = rurl.exec( s.url.toLowerCase() );
			s.crossDomain = !!( parts &&
				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
			);
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( state === 2 ) {
			return jqXHR;
		}

		// We can fire global events as of now if asked to
		fireGlobals = s.global;

		// Watch for a new set of requests
		if ( fireGlobals && jQuery.active++ === 0 ) {
			jQuery.event.trigger("ajaxStart");
		}

		// Uppercase the type
		s.type = s.type.toUpperCase();

		// Determine if request has content
		s.hasContent = !rnoContent.test( s.type );

		// Save the URL in case we're toying with the If-Modified-Since
		// and/or If-None-Match header later on
		cacheURL = s.url;

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// If data is available, append data to url
			if ( s.data ) {
				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
				// #9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add anti-cache in url if needed
			if ( s.cache === false ) {
				s.url = rts.test( cacheURL ) ?

					// If there is already a '_' parameter, set its value
					cacheURL.replace( rts, "$1_=" + nonce++ ) :

					// Otherwise add one to the end
					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
			}
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) {
			if ( jQuery.lastModified[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
			}
			if ( jQuery.etag[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
			}
		}

		// Set the correct header, if data is being sent
		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
			jqXHR.setRequestHeader( "Content-Type", s.contentType );
		}

		// Set the Accepts header for the server, depending on the dataType
		jqXHR.setRequestHeader(
			"Accept",
			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
				s.accepts[ "*" ]
		);

		// Check for headers option
		for ( i in s.headers ) {
			jqXHR.setRequestHeader( i, s.headers[ i ] );
		}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
			// Abort if not done already and return
			return jqXHR.abort();
		}

		// aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		for ( i in { success: 1, error: 1, complete: 1 } ) {
			jqXHR[ i ]( s[ i ] );
		}

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}
			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = setTimeout(function() {
					jqXHR.abort("timeout");
				}, s.timeout );
			}

			try {
				state = 1;
				transport.send( requestHeaders, done );
			} catch ( e ) {
				// Propagate exception as error if not done
				if ( state < 2 ) {
					done( -1, e );
				// Simply rethrow otherwise
				} else {
					throw e;
				}
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Called once
			if ( state === 2 ) {
				return;
			}

			// State is "done" now
			state = 2;

			// Clear timeout if it exists
			if ( timeoutTimer ) {
				clearTimeout( timeoutTimer );
			}

			// Dereference transport for early garbage collection
			// (no matter how long the jqXHR object will be used)
			transport = undefined;

			// Cache response headers
			responseHeadersString = headers || "";

			// Set readyState
			jqXHR.readyState = status > 0 ? 4 : 0;

			// Determine if successful
			isSuccess = status >= 200 && status < 300 || status === 304;

			// Get response data
			if ( responses ) {
				response = ajaxHandleResponses( s, jqXHR, responses );
			}

			// Convert no matter what (that way responseXXX fields are always set)
			response = ajaxConvert( s, response, jqXHR, isSuccess );

			// If successful, handle type chaining
			if ( isSuccess ) {

				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
				if ( s.ifModified ) {
					modified = jqXHR.getResponseHeader("Last-Modified");
					if ( modified ) {
						jQuery.lastModified[ cacheURL ] = modified;
					}
					modified = jqXHR.getResponseHeader("etag");
					if ( modified ) {
						jQuery.etag[ cacheURL ] = modified;
					}
				}

				// if no content
				if ( status === 204 || s.type === "HEAD" ) {
					statusText = "nocontent";

				// if not modified
				} else if ( status === 304 ) {
					statusText = "notmodified";

				// If we have data, let's convert it
				} else {
					statusText = response.state;
					success = response.data;
					error = response.error;
					isSuccess = !error;
				}
			} else {
				// We extract error from statusText
				// then normalize statusText and status for non-aborts
				error = statusText;
				if ( status || !statusText ) {
					statusText = "error";
					if ( status < 0 ) {
						status = 0;
					}
				}
			}

			// Set data for the fake xhr object
			jqXHR.status = status;
			jqXHR.statusText = ( nativeStatusText || statusText ) + "";

			// Success/Error
			if ( isSuccess ) {
				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
			} else {
				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
			}

			// Status-dependent callbacks
			jqXHR.statusCode( statusCode );
			statusCode = undefined;

			if ( fireGlobals ) {
				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
					[ jqXHR, s, isSuccess ? success : error ] );
			}

			// Complete
			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
				// Handle the global AJAX counter
				if ( !( --jQuery.active ) ) {
					jQuery.event.trigger("ajaxStop");
				}
			}
		}

		return jqXHR;
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get( url, data, callback, "json" );
	},

	getScript: function( url, callback ) {
		return jQuery.get( url, undefined, callback, "script" );
	}
});

jQuery.each( [ "get", "post" ], function( i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {
		// shift arguments if data argument was omitted
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		return jQuery.ajax({
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		});
	};
});

// Attach a bunch of functions for handling common AJAX events
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
	jQuery.fn[ type ] = function( fn ) {
		return this.on( type, fn );
	};
});


jQuery._evalUrl = function( url ) {
	return jQuery.ajax({
		url: url,
		type: "GET",
		dataType: "script",
		async: false,
		global: false,
		"throws": true
	});
};


jQuery.fn.extend({
	wrapAll: function( html ) {
		var wrap;

		if ( jQuery.isFunction( html ) ) {
			return this.each(function( i ) {
				jQuery( this ).wrapAll( html.call(this, i) );
			});
		}

		if ( this[ 0 ] ) {

			// The elements to wrap the target around
			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );

			if ( this[ 0 ].parentNode ) {
				wrap.insertBefore( this[ 0 ] );
			}

			wrap.map(function() {
				var elem = this;

				while ( elem.firstElementChild ) {
					elem = elem.firstElementChild;
				}

				return elem;
			}).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each(function( i ) {
				jQuery( this ).wrapInner( html.call(this, i) );
			});
		}

		return this.each(function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		});
	},

	wrap: function( html ) {
		var isFunction = jQuery.isFunction( html );

		return this.each(function( i ) {
			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
		});
	},

	unwrap: function() {
		return this.parent().each(function() {
			if ( !jQuery.nodeName( this, "body" ) ) {
				jQuery( this ).replaceWith( this.childNodes );
			}
		}).end();
	}
});


jQuery.expr.filters.hidden = function( elem ) {
	// Support: Opera <= 12.12
	// Opera reports offsetWidths and offsetHeights less than zero on some elements
	return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
};
jQuery.expr.filters.visible = function( elem ) {
	return !jQuery.expr.filters.hidden( elem );
};




var r20 = /%20/g,
	rbracket = /\[\]$/,
	rCRLF = /\r?\n/g,
	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
	rsubmittable = /^(?:input|select|textarea|keygen)/i;

function buildParams( prefix, obj, traditional, add ) {
	var name;

	if ( jQuery.isArray( obj ) ) {
		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {
				// Treat each array item as a scalar.
				add( prefix, v );

			} else {
				// Item is non-scalar (array or object), encode its numeric index.
				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
			}
		});

	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
		// Serialize object item.
		for ( name in obj ) {
			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
		}

	} else {
		// Serialize scalar item.
		add( prefix, obj );
	}
}

// Serialize an array of form elements or a set of
// key/values into a query string
jQuery.param = function( a, traditional ) {
	var prefix,
		s = [],
		add = function( key, value ) {
			// If value is a function, invoke it and return its value
			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
		};

	// Set traditional to true for jQuery <= 1.3.2 behavior.
	if ( traditional === undefined ) {
		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
	}

	// If an array was passed in, assume that it is an array of form elements.
	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		});

	} else {
		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" ).replace( r20, "+" );
};

jQuery.fn.extend({
	serialize: function() {
		return jQuery.param( this.serializeArray() );
	},
	serializeArray: function() {
		return this.map(function() {
			// Can add propHook for "elements" to filter or add form elements
			var elements = jQuery.prop( this, "elements" );
			return elements ? jQuery.makeArray( elements ) : this;
		})
		.filter(function() {
			var type = this.type;

			// Use .is( ":disabled" ) so that fieldset[disabled] works
			return this.name && !jQuery( this ).is( ":disabled" ) &&
				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
				( this.checked || !rcheckableType.test( type ) );
		})
		.map(function( i, elem ) {
			var val = jQuery( this ).val();

			return val == null ?
				null :
				jQuery.isArray( val ) ?
					jQuery.map( val, function( val ) {
						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
					}) :
					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		}).get();
	}
});


jQuery.ajaxSettings.xhr = function() {
	try {
		return new XMLHttpRequest();
	} catch( e ) {}
};

var xhrId = 0,
	xhrCallbacks = {},
	xhrSuccessStatus = {
		// file protocol always yields status code 0, assume 200
		0: 200,
		// Support: IE9
		// #1450: sometimes IE returns 1223 when it should be 204
		1223: 204
	},
	xhrSupported = jQuery.ajaxSettings.xhr();

// Support: IE9
// Open requests must be manually aborted on unload (#5280)
if ( window.ActiveXObject ) {
	jQuery( window ).on( "unload", function() {
		for ( var key in xhrCallbacks ) {
			xhrCallbacks[ key ]();
		}
	});
}

support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
support.ajax = xhrSupported = !!xhrSupported;

jQuery.ajaxTransport(function( options ) {
	var callback;

	// Cross domain only allowed if supported through XMLHttpRequest
	if ( support.cors || xhrSupported && !options.crossDomain ) {
		return {
			send: function( headers, complete ) {
				var i,
					xhr = options.xhr(),
					id = ++xhrId;

				xhr.open( options.type, options.url, options.async, options.username, options.password );

				// Apply custom fields if provided
				if ( options.xhrFields ) {
					for ( i in options.xhrFields ) {
						xhr[ i ] = options.xhrFields[ i ];
					}
				}

				// Override mime type if needed
				if ( options.mimeType && xhr.overrideMimeType ) {
					xhr.overrideMimeType( options.mimeType );
				}

				// X-Requested-With header
				// For cross-domain requests, seeing as conditions for a preflight are
				// akin to a jigsaw puzzle, we simply never set it to be sure.
				// (it can always be set on a per-request basis or even using ajaxSetup)
				// For same-domain requests, won't change header if already provided.
				if ( !options.crossDomain && !headers["X-Requested-With"] ) {
					headers["X-Requested-With"] = "XMLHttpRequest";
				}

				// Set headers
				for ( i in headers ) {
					xhr.setRequestHeader( i, headers[ i ] );
				}

				// Callback
				callback = function( type ) {
					return function() {
						if ( callback ) {
							delete xhrCallbacks[ id ];
							callback = xhr.onload = xhr.onerror = null;

							if ( type === "abort" ) {
								xhr.abort();
							} else if ( type === "error" ) {
								complete(
									// file: protocol always yields status 0; see #8605, #14207
									xhr.status,
									xhr.statusText
								);
							} else {
								complete(
									xhrSuccessStatus[ xhr.status ] || xhr.status,
									xhr.statusText,
									// Support: IE9
									// Accessing binary-data responseText throws an exception
									// (#11426)
									typeof xhr.responseText === "string" ? {
										text: xhr.responseText
									} : undefined,
									xhr.getAllResponseHeaders()
								);
							}
						}
					};
				};

				// Listen to events
				xhr.onload = callback();
				xhr.onerror = callback("error");

				// Create the abort callback
				callback = xhrCallbacks[ id ] = callback("abort");

				try {
					// Do send the request (this may raise an exception)
					xhr.send( options.hasContent && options.data || null );
				} catch ( e ) {
					// #14683: Only rethrow if this hasn't been notified as an error yet
					if ( callback ) {
						throw e;
					}
				}
			},

			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
});




// Install script dataType
jQuery.ajaxSetup({
	accepts: {
		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /(?:java|ecma)script/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
});

// Handle cache's special case and crossDomain
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
	}
});

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {
	// This transport only deals with cross domain requests
	if ( s.crossDomain ) {
		var script, callback;
		return {
			send: function( _, complete ) {
				script = jQuery("<script>").prop({
					async: true,
					charset: s.scriptCharset,
					src: s.url
				}).on(
					"load error",
					callback = function( evt ) {
						script.remove();
						callback = null;
						if ( evt ) {
							complete( evt.type === "error" ? 404 : 200, evt.type );
						}
					}
				);
				document.head.appendChild( script[ 0 ] );
			},
			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
});




var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings
jQuery.ajaxSetup({
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
		this[ callback ] = true;
		return callback;
	}
});

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
		);

	// Handle iff the expected data type is "jsonp" or we have a parameter to set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// Use data converter to retrieve json after script execution
		s.converters["script json"] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// force json dataType
		s.dataTypes[ 0 ] = "json";

		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// Clean-up function (fires after converters)
		jqXHR.always(function() {
			// Restore preexisting value
			window[ callbackName ] = overwritten;

			// Save back as free
			if ( s[ callbackName ] ) {
				// make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;

				// save the callback name for future use
				oldCallbacks.push( callbackName );
			}

			// Call if it was a function and we have a response
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		});

		// Delegate to script
		return "script";
	}
});




// data: string of html
// context (optional): If specified, the fragment will be created in this context, defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
jQuery.parseHTML = function( data, context, keepScripts ) {
	if ( !data || typeof data !== "string" ) {
		return null;
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}
	context = context || document;

	var parsed = rsingleTag.exec( data ),
		scripts = !keepScripts && [];

	// Single tag
	if ( parsed ) {
		return [ context.createElement( parsed[1] ) ];
	}

	parsed = jQuery.buildFragment( [ data ], context, scripts );

	if ( scripts && scripts.length ) {
		jQuery( scripts ).remove();
	}

	return jQuery.merge( [], parsed.childNodes );
};


// Keep a copy of the old load method
var _load = jQuery.fn.load;

/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
	if ( typeof url !== "string" && _load ) {
		return _load.apply( this, arguments );
	}

	var selector, type, response,
		self = this,
		off = url.indexOf(" ");

	if ( off >= 0 ) {
		selector = jQuery.trim( url.slice( off ) );
		url = url.slice( 0, off );
	}

	// If it's a function
	if ( jQuery.isFunction( params ) ) {

		// We assume that it's the callback
		callback = params;
		params = undefined;

	// Otherwise, build a param string
	} else if ( params && typeof params === "object" ) {
		type = "POST";
	}

	// If we have elements to modify, make the request
	if ( self.length > 0 ) {
		jQuery.ajax({
			url: url,

			// if "type" variable is undefined, then "GET" method will be used
			type: type,
			dataType: "html",
			data: params
		}).done(function( responseText ) {

			// Save response for use in complete callback
			response = arguments;

			self.html( selector ?

				// If a selector was specified, locate the right elements in a dummy div
				// Exclude scripts to avoid IE 'Permission Denied' errors
				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :

				// Otherwise use the full result
				responseText );

		}).complete( callback && function( jqXHR, status ) {
			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
		});
	}

	return this;
};




jQuery.expr.filters.animated = function( elem ) {
	return jQuery.grep(jQuery.timers, function( fn ) {
		return elem === fn.elem;
	}).length;
};




var docElem = window.document.documentElement;

/**
 * Gets a window from an element
 */
function getWindow( elem ) {
	return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
}

jQuery.offset = {
	setOffset: function( elem, options, i ) {
		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
			position = jQuery.css( elem, "position" ),
			curElem = jQuery( elem ),
			props = {};

		// Set position first, in-case top/left are set even on static elem
		if ( position === "static" ) {
			elem.style.position = "relative";
		}

		curOffset = curElem.offset();
		curCSSTop = jQuery.css( elem, "top" );
		curCSSLeft = jQuery.css( elem, "left" );
		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
			( curCSSTop + curCSSLeft ).indexOf("auto") > -1;

		// Need to be able to calculate position if either top or left is auto and position is either absolute or fixed
		if ( calculatePosition ) {
			curPosition = curElem.position();
			curTop = curPosition.top;
			curLeft = curPosition.left;

		} else {
			curTop = parseFloat( curCSSTop ) || 0;
			curLeft = parseFloat( curCSSLeft ) || 0;
		}

		if ( jQuery.isFunction( options ) ) {
			options = options.call( elem, i, curOffset );
		}

		if ( options.top != null ) {
			props.top = ( options.top - curOffset.top ) + curTop;
		}
		if ( options.left != null ) {
			props.left = ( options.left - curOffset.left ) + curLeft;
		}

		if ( "using" in options ) {
			options.using.call( elem, props );

		} else {
			curElem.css( props );
		}
	}
};

jQuery.fn.extend({
	offset: function( options ) {
		if ( arguments.length ) {
			return options === undefined ?
				this :
				this.each(function( i ) {
					jQuery.offset.setOffset( this, options, i );
				});
		}

		var docElem, win,
			elem = this[ 0 ],
			box = { top: 0, left: 0 },
			doc = elem && elem.ownerDocument;

		if ( !doc ) {
			return;
		}

		docElem = doc.documentElement;

		// Make sure it's not a disconnected DOM node
		if ( !jQuery.contains( docElem, elem ) ) {
			return box;
		}

		// If we don't have gBCR, just use 0,0 rather than error
		// BlackBerry 5, iOS 3 (original iPhone)
		if ( typeof elem.getBoundingClientRect !== strundefined ) {
			box = elem.getBoundingClientRect();
		}
		win = getWindow( doc );
		return {
			top: box.top + win.pageYOffset - docElem.clientTop,
			left: box.left + win.pageXOffset - docElem.clientLeft
		};
	},

	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset,
			elem = this[ 0 ],
			parentOffset = { top: 0, left: 0 };

		// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
		if ( jQuery.css( elem, "position" ) === "fixed" ) {
			// We assume that getBoundingClientRect is available when computed position is fixed
			offset = elem.getBoundingClientRect();

		} else {
			// Get *real* offsetParent
			offsetParent = this.offsetParent();

			// Get correct offsets
			offset = this.offset();
			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
				parentOffset = offsetParent.offset();
			}

			// Add offsetParent borders
			parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
		}

		// Subtract parent offsets and element margins
		return {
			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
		};
	},

	offsetParent: function() {
		return this.map(function() {
			var offsetParent = this.offsetParent || docElem;

			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
				offsetParent = offsetParent.offsetParent;
			}

			return offsetParent || docElem;
		});
	}
});

// Create scrollLeft and scrollTop methods
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
	var top = "pageYOffset" === prop;

	jQuery.fn[ method ] = function( val ) {
		return access( this, function( elem, method, val ) {
			var win = getWindow( elem );

			if ( val === undefined ) {
				return win ? win[ prop ] : elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : window.pageXOffset,
					top ? val : window.pageYOffset
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length, null );
	};
});

// Add the top/left cssHooks using jQuery.fn.position
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// getComputedStyle returns percent when specified for top/left/bottom/right
// rather than make the css module depend on the offset module, we just check for it here
jQuery.each( [ "top", "left" ], function( i, prop ) {
	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
		function( elem, computed ) {
			if ( computed ) {
				computed = curCSS( elem, prop );
				// if curCSS returns percentage, fallback to offset
				return rnumnonpx.test( computed ) ?
					jQuery( elem ).position()[ prop ] + "px" :
					computed;
			}
		}
	);
});


// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
		// margin is only for outerHeight, outerWidth
		jQuery.fn[ funcName ] = function( margin, value ) {
			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

			return access( this, function( elem, type, value ) {
				var doc;

				if ( jQuery.isWindow( elem ) ) {
					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
					// isn't a whole lot we can do. See pull request at this URL for discussion:
					// https://github.com/jquery/jquery/pull/764
					return elem.document.documentElement[ "client" + name ];
				}

				// Get document width or height
				if ( elem.nodeType === 9 ) {
					doc = elem.documentElement;

					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
					// whichever is greatest
					return Math.max(
						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
						elem.body[ "offset" + name ], doc[ "offset" + name ],
						doc[ "client" + name ]
					);
				}

				return value === undefined ?
					// Get width or height on the element, requesting but not forcing parseFloat
					jQuery.css( elem, type, extra ) :

					// Set width or height on the element
					jQuery.style( elem, type, value, extra );
			}, type, chainable ? margin : undefined, chainable, null );
		};
	});
});


// The number of elements contained in the matched element set
jQuery.fn.size = function() {
	return this.length;
};

jQuery.fn.andSelf = jQuery.fn.addBack;




// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.

// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

if ( typeof define === "function" && define.amd ) {
	define( "jquery", [], function() {
		return jQuery;
	});
}




var
	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,

	// Map over the $ in case of overwrite
	_$ = window.$;

jQuery.noConflict = function( deep ) {
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
};

// Expose jQuery and $ identifiers, even in
// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if ( typeof noGlobal === strundefined ) {
	window.jQuery = window.$ = jQuery;
}




return jQuery;

}));

(function() {
var define, requireModule, require, requirejs;

(function() {
  var registry = {}, seen = {};

  define = function(name, deps, callback) {
    registry[name] = { deps: deps, callback: callback };
  };

  requirejs = require = requireModule = function(name) {
  requirejs._eak_seen = registry;

    if (seen[name]) { return seen[name]; }
    seen[name] = {};

    if (!registry[name]) {
      throw new Error("Could not find module " + name);
    }

    var mod = registry[name],
        deps = mod.deps,
        callback = mod.callback,
        reified = [],
        exports;

    for (var i=0, l=deps.length; i<l; i++) {
      if (deps[i] === 'exports') {
        reified.push(exports = {});
      } else {
        reified.push(requireModule(resolve(deps[i])));
      }
    }

    var value = callback.apply(this, reified);
    return seen[name] = exports || value;

    function resolve(child) {
      if (child.charAt(0) !== '.') { return child; }
      var parts = child.split("/");
      var parentBase = name.split("/").slice(0, -1);

      for (var i=0, l=parts.length; i<l; i++) {
        var part = parts[i];

        if (part === '..') { parentBase.pop(); }
        else if (part === '.') { continue; }
        else { parentBase.push(part); }
      }

      return parentBase.join("/");
    }
  };
})();

define("promise/all", 
  ["./utils","exports"],
  function(__dependency1__, __exports__) {
    "use strict";
    /* global toString */

    var isArray = __dependency1__.isArray;
    var isFunction = __dependency1__.isFunction;

    /**
      Returns a promise that is fulfilled when all the given promises have been
      fulfilled, or rejected if any of them become rejected. The return promise
      is fulfilled with an array that gives all the values in the order they were
      passed in the `promises` array argument.

      Example:

      ```javascript
      var promise1 = RSVP.resolve(1);
      var promise2 = RSVP.resolve(2);
      var promise3 = RSVP.resolve(3);
      var promises = [ promise1, promise2, promise3 ];

      RSVP.all(promises).then(function(array){
        // The array here would be [ 1, 2, 3 ];
      });
      ```

      If any of the `promises` given to `RSVP.all` are rejected, the first promise
      that is rejected will be given as an argument to the returned promises's
      rejection handler. For example:

      Example:

      ```javascript
      var promise1 = RSVP.resolve(1);
      var promise2 = RSVP.reject(new Error("2"));
      var promise3 = RSVP.reject(new Error("3"));
      var promises = [ promise1, promise2, promise3 ];

      RSVP.all(promises).then(function(array){
        // Code here never runs because there are rejected promises!
      }, function(error) {
        // error.message === "2"
      });
      ```

      @method all
      @for RSVP
      @param {Array} promises
      @param {String} label
      @return {Promise} promise that is fulfilled when all `promises` have been
      fulfilled, or rejected if any of them become rejected.
    */
    function all(promises) {
      /*jshint validthis:true */
      var Promise = this;

      if (!isArray(promises)) {
        throw new TypeError('You must pass an array to all.');
      }

      return new Promise(function(resolve, reject) {
        var results = [], remaining = promises.length,
        promise;

        if (remaining === 0) {
          resolve([]);
        }

        function resolver(index) {
          return function(value) {
            resolveAll(index, value);
          };
        }

        function resolveAll(index, value) {
          results[index] = value;
          if (--remaining === 0) {
            resolve(results);
          }
        }

        for (var i = 0; i < promises.length; i++) {
          promise = promises[i];

          if (promise && isFunction(promise.then)) {
            promise.then(resolver(i), reject);
          } else {
            resolveAll(i, promise);
          }
        }
      });
    }

    __exports__.all = all;
  });
define("promise/asap", 
  ["exports"],
  function(__exports__) {
    "use strict";
    var browserGlobal = (typeof window !== 'undefined') ? window : {};
    var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
    var local = (typeof global !== 'undefined') ? global : (this === undefined? window:this);

    // node
    function useNextTick() {
      return function() {
        process.nextTick(flush);
      };
    }

    function useMutationObserver() {
      var iterations = 0;
      var observer = new BrowserMutationObserver(flush);
      var node = document.createTextNode('');
      observer.observe(node, { characterData: true });

      return function() {
        node.data = (iterations = ++iterations % 2);
      };
    }

    function useSetTimeout() {
      return function() {
        local.setTimeout(flush, 1);
      };
    }

    var queue = [];
    function flush() {
      for (var i = 0; i < queue.length; i++) {
        var tuple = queue[i];
        var callback = tuple[0], arg = tuple[1];
        callback(arg);
      }
      queue = [];
    }

    var scheduleFlush;

    // Decide what async method to use to triggering processing of queued callbacks:
    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
      scheduleFlush = useNextTick();
    } else if (BrowserMutationObserver) {
      scheduleFlush = useMutationObserver();
    } else {
      scheduleFlush = useSetTimeout();
    }

    function asap(callback, arg) {
      var length = queue.push([callback, arg]);
      if (length === 1) {
        // If length is 1, that means that we need to schedule an async flush.
        // If additional callbacks are queued before the queue is flushed, they
        // will be processed by this flush that we are scheduling.
        scheduleFlush();
      }
    }

    __exports__.asap = asap;
  });
define("promise/config", 
  ["exports"],
  function(__exports__) {
    "use strict";
    var config = {
      instrument: false
    };

    function configure(name, value) {
      if (arguments.length === 2) {
        config[name] = value;
      } else {
        return config[name];
      }
    }

    __exports__.config = config;
    __exports__.configure = configure;
  });
define("promise/polyfill", 
  ["./promise","./utils","exports"],
  function(__dependency1__, __dependency2__, __exports__) {
    "use strict";
    /*global self*/
    var RSVPPromise = __dependency1__.Promise;
    var isFunction = __dependency2__.isFunction;

    function polyfill() {
      var local;

      if (typeof global !== 'undefined') {
        local = global;
      } else if (typeof window !== 'undefined' && window.document) {
        local = window;
      } else {
        local = self;
      }

      var es6PromiseSupport = 
        "Promise" in local &&
        // Some of these methods are missing from
        // Firefox/Chrome experimental implementations
        "resolve" in local.Promise &&
        "reject" in local.Promise &&
        "all" in local.Promise &&
        "race" in local.Promise &&
        // Older version of the spec had a resolver object
        // as the arg rather than a function
        (function() {
          var resolve;
          new local.Promise(function(r) { resolve = r; });
          return isFunction(resolve);
        }());

      // !es6PromiseSupport || ~window.location.href.indexOf('rsvpromise')
      if (true) {
        local.Promise = RSVPPromise;
      }
    }

    __exports__.polyfill = polyfill;
  });
define("promise/promise", 
  ["./config","./utils","./all","./race","./resolve","./reject","./asap","exports"],
  function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
    "use strict";
    var config = __dependency1__.config;
    var configure = __dependency1__.configure;
    var objectOrFunction = __dependency2__.objectOrFunction;
    var isFunction = __dependency2__.isFunction;
    var now = __dependency2__.now;
    var all = __dependency3__.all;
    var race = __dependency4__.race;
    var staticResolve = __dependency5__.resolve;
    var staticReject = __dependency6__.reject;
    var asap = __dependency7__.asap;

    var counter = 0;

    config.async = asap; // default async is asap;

    function Promise(resolver) {
      if (!isFunction(resolver)) {
        throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
      }

      if (!(this instanceof Promise)) {
        throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
      }

      this._subscribers = [];

      invokeResolver(resolver, this);
    }

    function invokeResolver(resolver, promise) {
      function resolvePromise(value) {
        resolve(promise, value);
      }

      function rejectPromise(reason) {
        reject(promise, reason);
      }

      try {
        resolver(resolvePromise, rejectPromise);
      } catch(e) {
        rejectPromise(e);
      }
    }

    function invokeCallback(settled, promise, callback, detail) {
      var hasCallback = isFunction(callback),
          value, error, succeeded, failed;

      if (hasCallback) {
        try {
          value = callback(detail);
          succeeded = true;
        } catch(e) {
          failed = true;
          error = e;
        }
      } else {
        value = detail;
        succeeded = true;
      }

      if (handleThenable(promise, value)) {
        return;
      } else if (hasCallback && succeeded) {
        resolve(promise, value);
      } else if (failed) {
        reject(promise, error);
      } else if (settled === FULFILLED) {
        resolve(promise, value);
      } else if (settled === REJECTED) {
        reject(promise, value);
      }
    }

    var PENDING   = void 0;
    var SEALED    = 0;
    var FULFILLED = 1;
    var REJECTED  = 2;

    function subscribe(parent, child, onFulfillment, onRejection) {
      var subscribers = parent._subscribers;
      var length = subscribers.length;

      subscribers[length] = child;
      subscribers[length + FULFILLED] = onFulfillment;
      subscribers[length + REJECTED]  = onRejection;
    }

    function publish(promise, settled) {
      var child, callback, subscribers = promise._subscribers, detail = promise._detail;

      for (var i = 0; i < subscribers.length; i += 3) {
        child = subscribers[i];
        callback = subscribers[i + settled];

        invokeCallback(settled, child, callback, detail);
      }

      promise._subscribers = null;
    }

    Promise.prototype = {
      constructor: Promise,

      _state: undefined,
      _detail: undefined,
      _subscribers: undefined,

      then: function(onFulfillment, onRejection) {
        var promise = this;

        var thenPromise = new this.constructor(function() {});

        if (this._state) {
          var callbacks = arguments;
          config.async(function invokePromiseCallback() {
            invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
          });
        } else {
          subscribe(this, thenPromise, onFulfillment, onRejection);
        }

        return thenPromise;
      },

      'catch': function(onRejection) {
        return this.then(null, onRejection);
      }
    };

    Promise.all = all;
    Promise.race = race;
    Promise.resolve = staticResolve;
    Promise.reject = staticReject;

    function handleThenable(promise, value) {
      var then = null,
      resolved;

      try {
        if (promise === value) {
          throw new TypeError("A promises callback cannot return that same promise.");
        }

        if (objectOrFunction(value)) {
          then = value.then;

          if (isFunction(then)) {
            then.call(value, function(val) {
              if (resolved) { return true; }
              resolved = true;

              if (value !== val) {
                resolve(promise, val);
              } else {
                fulfill(promise, val);
              }
            }, function(val) {
              if (resolved) { return true; }
              resolved = true;

              reject(promise, val);
            });

            return true;
          }
        }
      } catch (error) {
        if (resolved) { return true; }
        reject(promise, error);
        return true;
      }

      return false;
    }

    function resolve(promise, value) {
      if (promise === value) {
        fulfill(promise, value);
      } else if (!handleThenable(promise, value)) {
        fulfill(promise, value);
      }
    }

    function fulfill(promise, value) {
      if (promise._state !== PENDING) { return; }
      promise._state = SEALED;
      promise._detail = value;

      config.async(publishFulfillment, promise);
    }

    function reject(promise, reason) {
      if (promise._state !== PENDING) { return; }
      promise._state = SEALED;
      promise._detail = reason;

      config.async(publishRejection, promise);
    }

    function publishFulfillment(promise) {
      publish(promise, promise._state = FULFILLED);
    }

    function publishRejection(promise) {
      publish(promise, promise._state = REJECTED);
    }

    __exports__.Promise = Promise;
  });
define("promise/race", 
  ["./utils","exports"],
  function(__dependency1__, __exports__) {
    "use strict";
    /* global toString */
    var isArray = __dependency1__.isArray;

    /**
      `RSVP.race` allows you to watch a series of promises and act as soon as the
      first promise given to the `promises` argument fulfills or rejects.

      Example:

      ```javascript
      var promise1 = new RSVP.Promise(function(resolve, reject){
        setTimeout(function(){
          resolve("promise 1");
        }, 200);
      });

      var promise2 = new RSVP.Promise(function(resolve, reject){
        setTimeout(function(){
          resolve("promise 2");
        }, 100);
      });

      RSVP.race([promise1, promise2]).then(function(result){
        // result === "promise 2" because it was resolved before promise1
        // was resolved.
      });
      ```

      `RSVP.race` is deterministic in that only the state of the first completed
      promise matters. For example, even if other promises given to the `promises`
      array argument are resolved, but the first completed promise has become
      rejected before the other promises became fulfilled, the returned promise
      will become rejected:

      ```javascript
      var promise1 = new RSVP.Promise(function(resolve, reject){
        setTimeout(function(){
          resolve("promise 1");
        }, 200);
      });

      var promise2 = new RSVP.Promise(function(resolve, reject){
        setTimeout(function(){
          reject(new Error("promise 2"));
        }, 100);
      });

      RSVP.race([promise1, promise2]).then(function(result){
        // Code here never runs because there are rejected promises!
      }, function(reason){
        // reason.message === "promise2" because promise 2 became rejected before
        // promise 1 became fulfilled
      });
      ```

      @method race
      @for RSVP
      @param {Array} promises array of promises to observe
      @param {String} label optional string for describing the promise returned.
      Useful for tooling.
      @return {Promise} a promise that becomes fulfilled with the value the first
      completed promises is resolved with if the first completed promise was
      fulfilled, or rejected with the reason that the first completed promise
      was rejected with.
    */
    function race(promises) {
      /*jshint validthis:true */
      var Promise = this;

      if (!isArray(promises)) {
        throw new TypeError('You must pass an array to race.');
      }
      return new Promise(function(resolve, reject) {
        var results = [], promise;

        for (var i = 0; i < promises.length; i++) {
          promise = promises[i];

          if (promise && typeof promise.then === 'function') {
            promise.then(resolve, reject);
          } else {
            resolve(promise);
          }
        }
      });
    }

    __exports__.race = race;
  });
define("promise/reject", 
  ["exports"],
  function(__exports__) {
    "use strict";
    /**
      `RSVP.reject` returns a promise that will become rejected with the passed
      `reason`. `RSVP.reject` is essentially shorthand for the following:

      ```javascript
      var promise = new RSVP.Promise(function(resolve, reject){
        reject(new Error('WHOOPS'));
      });

      promise.then(function(value){
        // Code here doesn't run because the promise is rejected!
      }, function(reason){
        // reason.message === 'WHOOPS'
      });
      ```

      Instead of writing the above, your code now simply becomes the following:

      ```javascript
      var promise = RSVP.reject(new Error('WHOOPS'));

      promise.then(function(value){
        // Code here doesn't run because the promise is rejected!
      }, function(reason){
        // reason.message === 'WHOOPS'
      });
      ```

      @method reject
      @for RSVP
      @param {Any} reason value that the returned promise will be rejected with.
      @param {String} label optional string for identifying the returned promise.
      Useful for tooling.
      @return {Promise} a promise that will become rejected with the given
      `reason`.
    */
    function reject(reason) {
      /*jshint validthis:true */
      var Promise = this;

      return new Promise(function (resolve, reject) {
        reject(reason);
      });
    }

    __exports__.reject = reject;
  });
define("promise/resolve", 
  ["exports"],
  function(__exports__) {
    "use strict";
    function resolve(value) {
      /*jshint validthis:true */
      if (value && typeof value === 'object' && value.constructor === this) {
        return value;
      }

      var Promise = this;

      return new Promise(function(resolve) {
        resolve(value);
      });
    }

    __exports__.resolve = resolve;
  });
define("promise/utils", 
  ["exports"],
  function(__exports__) {
    "use strict";
    function objectOrFunction(x) {
      return isFunction(x) || (typeof x === "object" && x !== null);
    }

    function isFunction(x) {
      return typeof x === "function";
    }

    function isArray(x) {
      return Object.prototype.toString.call(x) === "[object Array]";
    }

    // Date.now is not available in browsers < IE9
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
    var now = Date.now || function() { return new Date().getTime(); };


    __exports__.objectOrFunction = objectOrFunction;
    __exports__.isFunction = isFunction;
    __exports__.isArray = isArray;
    __exports__.now = now;
  });
requireModule('promise/polyfill').polyfill();
}());
/*
 ### jQuery XML to JSON Plugin v1.3 - 2013-02-18 ###
 * http://www.fyneworks.com/ - diego@fyneworks.com
	* Licensed under http://en.wikipedia.org/wiki/MIT_License
 ###
 Website: http://www.fyneworks.com/jquery/xml-to-json/
*//*
 # INSPIRED BY: http://www.terracoder.com/
           AND: http://www.thomasfrank.se/xml_to_json.html
											AND: http://www.kawa.net/works/js/xml/objtree-e.html
*//*
 This simple script converts XML (document of code) into a JSON object. It is the combination of 2
 'xml to json' great parsers (see below) which allows for both 'simple' and 'extended' parsing modes.
*/
// Avoid collisions
;if(window.jQuery) (function($){
 
 // Add function to jQuery namespace
 $.extend({
  
  // converts xml documents and xml text to json object
  xml2json: function(xml, extended) {
   if(!xml) return {}; // quick fail
   
   //### PARSER LIBRARY
   // Core function
   function parseXML(node, simple){
    if(!node) return null;
    var txt = '', obj = null, att = null;
    var nt = node.nodeType, nn = jsVar(node.localName || node.nodeName);
    var nv = node.text || node.nodeValue || '';
    /*DBG*/ //if(window.console) console.log(['x2j',nn,nt,nv.length+' bytes']);
    if(node.childNodes){
     if(node.childNodes.length>0){
      /*DBG*/ //if(window.console) console.log(['x2j',nn,'CHILDREN',node.childNodes]);
      $.each(node.childNodes, function(n,cn){
       var cnt = cn.nodeType, cnn = jsVar(cn.localName || cn.nodeName);
       var cnv = cn.text || cn.nodeValue || '';
       /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>a',cnn,cnt,cnv]);
       if(cnt == 8){
        /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>b',cnn,'COMMENT (ignore)']);
        return; // ignore comment node
       }
       else if(cnt == 3 || cnt == 4 || !cnn){
        // ignore white-space in between tags
        if(cnv.match(/^\s+$/)){
         /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>c',cnn,'WHITE-SPACE (ignore)']);
         return;
        };
        /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>d',cnn,'TEXT']);
        txt += cnv.replace(/^\s+/,'').replace(/\s+$/,'');
								// make sure we ditch trailing spaces from markup
       }
       else{
        /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>e',cnn,'OBJECT']);
        obj = obj || {};
        if(obj[cnn]){
         /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>f',cnn,'ARRAY']);
         
									// http://forum.jquery.com/topic/jquery-jquery-xml2json-problems-when-siblings-of-the-same-tagname-only-have-a-textnode-as-a-child
									if(!obj[cnn].length) obj[cnn] = myArr(obj[cnn]);
									obj[cnn] = myArr(obj[cnn]);
         
									obj[cnn][ obj[cnn].length ] = parseXML(cn, true/* simple */);
         obj[cnn].length = obj[cnn].length;
        }
        else{
         /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>g',cnn,'dig deeper...']);
         obj[cnn] = parseXML(cn);
        };
       };
      });
     };//node.childNodes.length>0
    };//node.childNodes
    if(node.attributes){
     if(node.attributes.length>0){
      /*DBG*/ //if(window.console) console.log(['x2j',nn,'ATTRIBUTES',node.attributes])
      att = {}; obj = obj || {};
      $.each(node.attributes, function(a,at){
       var atn = jsVar(at.name), atv = at.value;
       att[atn] = atv;
       if(obj[atn]){
        /*DBG*/ //if(window.console) console.log(['x2j',nn,'attr>',atn,'ARRAY']);
        
								// http://forum.jquery.com/topic/jquery-jquery-xml2json-problems-when-siblings-of-the-same-tagname-only-have-a-textnode-as-a-child
								//if(!obj[atn].length) obj[atn] = myArr(obj[atn]);//[ obj[ atn ] ];
        obj[cnn] = myArr(obj[cnn]);
								
								obj[atn][ obj[atn].length ] = atv;
        obj[atn].length = obj[atn].length;
       }
       else{
        /*DBG*/ //if(window.console) console.log(['x2j',nn,'attr>',atn,'TEXT']);
        obj[atn] = atv;
       };
      });
      //obj['attributes'] = att;
     };//node.attributes.length>0
    };//node.attributes
    if(obj){
     obj = $.extend( (txt!='' ? new String(txt) : {}),/* {text:txt},*/ obj || {}/*, att || {}*/);
     //txt = (obj.text) ? (typeof(obj.text)=='object' ? obj.text : [obj.text || '']).concat([txt]) : txt;
     txt = (obj.text) ? ([obj.text || '']).concat([txt]) : txt;
     if(txt) obj.text = txt;
     txt = '';
    };
    var out = obj || txt;
    //console.log([extended, simple, out]);
    if(extended){
     if(txt) out = {};//new String(out);
     txt = out.text || txt || '';
     if(txt) out.text = txt;
     if(!simple) out = myArr(out);
    };
    return out;
   };// parseXML
   // Core Function End
   // Utility functions
   var jsVar = function(s){ return String(s || '').replace(/-/g,"_"); };
   
			// NEW isNum function: 01/09/2010
			// Thanks to Emile Grau, GigaTecnologies S.L., www.gigatransfer.com, www.mygigamail.com
			function isNum(s){
				// based on utility function isNum from xml2json plugin (http://www.fyneworks.com/ - diego@fyneworks.com)
				// few bugs corrected from original function :
				// - syntax error : regexp.test(string) instead of string.test(reg)
				// - regexp modified to accept  comma as decimal mark (latin syntax : 25,24 )
				// - regexp modified to reject if no number before decimal mark  : ".7" is not accepted
				// - string is "trimmed", allowing to accept space at the beginning and end of string
				var regexp=/^((-)?([0-9]+)(([\.\,]{0,1})([0-9]+))?$)/
				return (typeof s == "number") || regexp.test(String((s && typeof s == "string") ? jQuery.trim(s) : ''));
			};
			// OLD isNum function: (for reference only)
			//var isNum = function(s){ return (typeof s == "number") || String((s && typeof s == "string") ? s : '').test(/^((-)?([0-9]*)((\.{0,1})([0-9]+))?$)/); };
																
   var myArr = function(o){
    
				// http://forum.jquery.com/topic/jquery-jquery-xml2json-problems-when-siblings-of-the-same-tagname-only-have-a-textnode-as-a-child
				//if(!o.length) o = [ o ]; o.length=o.length;
    if(!$.isArray(o)) o = [ o ]; o.length=o.length;
				
				// here is where you can attach additional functionality, such as searching and sorting...
    return o;
   };
   // Utility functions End
   //### PARSER LIBRARY END
   
   // Convert plain text to xml
   if(typeof xml=='string') xml = $.text2xml(xml);
   
   // Quick fail if not xml (or if this is a node)
   if(!xml.nodeType) return;
   if(xml.nodeType == 3 || xml.nodeType == 4) return xml.nodeValue;
   
   // Find xml root node
   var root = (xml.nodeType == 9) ? xml.documentElement : xml;
   
   // Convert xml to json
   var out = parseXML(root, true /* simple */);
   
   // Clean-up memory
   xml = null; root = null;
   
   // Send output
   return out;
  },
  
  // Convert text to XML DOM
  text2xml: function(str) {
   // NOTE: I'd like to use jQuery for this, but jQuery makes all tags uppercase
   //return $(xml)[0];
   
   /* prior to jquery 1.9 */
   /*
   var out;
   try{
    var xml = ((!$.support.opacity && !$.support.style))?new ActiveXObject("Microsoft.XMLDOM"):new DOMParser();
    xml.async = false;
   }catch(e){ throw new Error("XML Parser could not be instantiated") };
   try{
    if((!$.support.opacity && !$.support.style)) out = (xml.loadXML(str))?xml:false;
    else out = xml.parseFromString(str, "text/xml");
   }catch(e){ throw new Error("Error parsing XML string") };
   return out;
   */

   /* jquery 1.9+ */
   return $.parseXML(str);
  }
		
 }); // extend $

})(jQuery);
(function(t,e){if(typeof define==="function"&&define.amd){define(["jquery"],e)}else if(typeof exports==="object"){module.exports=e(require("jquery"))}else{e(t.jQuery)}})(this,function(t){t.transit={version:"0.9.12",propertyMap:{marginLeft:"margin",marginRight:"margin",marginBottom:"margin",marginTop:"margin",paddingLeft:"padding",paddingRight:"padding",paddingBottom:"padding",paddingTop:"padding"},enabled:true,useTransitionEnd:false};var e=document.createElement("div");var n={};function i(t){if(t in e.style)return t;var n=["Moz","Webkit","O","ms"];var i=t.charAt(0).toUpperCase()+t.substr(1);for(var r=0;r<n.length;++r){var s=n[r]+i;if(s in e.style){return s}}}function r(){e.style[n.transform]="";e.style[n.transform]="rotateY(90deg)";return e.style[n.transform]!==""}var s=navigator.userAgent.toLowerCase().indexOf("chrome")>-1;n.transition=i("transition");n.transitionDelay=i("transitionDelay");n.transform=i("transform");n.transformOrigin=i("transformOrigin");n.filter=i("Filter");n.transform3d=r();var a={transition:"transitionend",MozTransition:"transitionend",OTransition:"oTransitionEnd",WebkitTransition:"webkitTransitionEnd",msTransition:"MSTransitionEnd"};var o=n.transitionEnd=a[n.transition]||null;for(var u in n){if(n.hasOwnProperty(u)&&typeof t.support[u]==="undefined"){t.support[u]=n[u]}}e=null;t.cssEase={_default:"ease","in":"ease-in",out:"ease-out","in-out":"ease-in-out",snap:"cubic-bezier(0,1,.5,1)",easeInCubic:"cubic-bezier(.550,.055,.675,.190)",easeOutCubic:"cubic-bezier(.215,.61,.355,1)",easeInOutCubic:"cubic-bezier(.645,.045,.355,1)",easeInCirc:"cubic-bezier(.6,.04,.98,.335)",easeOutCirc:"cubic-bezier(.075,.82,.165,1)",easeInOutCirc:"cubic-bezier(.785,.135,.15,.86)",easeInExpo:"cubic-bezier(.95,.05,.795,.035)",easeOutExpo:"cubic-bezier(.19,1,.22,1)",easeInOutExpo:"cubic-bezier(1,0,0,1)",easeInQuad:"cubic-bezier(.55,.085,.68,.53)",easeOutQuad:"cubic-bezier(.25,.46,.45,.94)",easeInOutQuad:"cubic-bezier(.455,.03,.515,.955)",easeInQuart:"cubic-bezier(.895,.03,.685,.22)",easeOutQuart:"cubic-bezier(.165,.84,.44,1)",easeInOutQuart:"cubic-bezier(.77,0,.175,1)",easeInQuint:"cubic-bezier(.755,.05,.855,.06)",easeOutQuint:"cubic-bezier(.23,1,.32,1)",easeInOutQuint:"cubic-bezier(.86,0,.07,1)",easeInSine:"cubic-bezier(.47,0,.745,.715)",easeOutSine:"cubic-bezier(.39,.575,.565,1)",easeInOutSine:"cubic-bezier(.445,.05,.55,.95)",easeInBack:"cubic-bezier(.6,-.28,.735,.045)",easeOutBack:"cubic-bezier(.175, .885,.32,1.275)",easeInOutBack:"cubic-bezier(.68,-.55,.265,1.55)"};t.cssHooks["transit:transform"]={get:function(e){return t(e).data("transform")||new f},set:function(e,i){var r=i;if(!(r instanceof f)){r=new f(r)}if(n.transform==="WebkitTransform"&&!s){e.style[n.transform]=r.toString(true)}else{e.style[n.transform]=r.toString()}t(e).data("transform",r)}};t.cssHooks.transform={set:t.cssHooks["transit:transform"].set};t.cssHooks.filter={get:function(t){return t.style[n.filter]},set:function(t,e){t.style[n.filter]=e}};if(t.fn.jquery<"1.8"){t.cssHooks.transformOrigin={get:function(t){return t.style[n.transformOrigin]},set:function(t,e){t.style[n.transformOrigin]=e}};t.cssHooks.transition={get:function(t){return t.style[n.transition]},set:function(t,e){t.style[n.transition]=e}}}p("scale");p("scaleX");p("scaleY");p("translate");p("rotate");p("rotateX");p("rotateY");p("rotate3d");p("perspective");p("skewX");p("skewY");p("x",true);p("y",true);function f(t){if(typeof t==="string"){this.parse(t)}return this}f.prototype={setFromString:function(t,e){var n=typeof e==="string"?e.split(","):e.constructor===Array?e:[e];n.unshift(t);f.prototype.set.apply(this,n)},set:function(t){var e=Array.prototype.slice.apply(arguments,[1]);if(this.setter[t]){this.setter[t].apply(this,e)}else{this[t]=e.join(",")}},get:function(t){if(this.getter[t]){return this.getter[t].apply(this)}else{return this[t]||0}},setter:{rotate:function(t){this.rotate=b(t,"deg")},rotateX:function(t){this.rotateX=b(t,"deg")},rotateY:function(t){this.rotateY=b(t,"deg")},scale:function(t,e){if(e===undefined){e=t}this.scale=t+","+e},skewX:function(t){this.skewX=b(t,"deg")},skewY:function(t){this.skewY=b(t,"deg")},perspective:function(t){this.perspective=b(t,"px")},x:function(t){this.set("translate",t,null)},y:function(t){this.set("translate",null,t)},translate:function(t,e){if(this._translateX===undefined){this._translateX=0}if(this._translateY===undefined){this._translateY=0}if(t!==null&&t!==undefined){this._translateX=b(t,"px")}if(e!==null&&e!==undefined){this._translateY=b(e,"px")}this.translate=this._translateX+","+this._translateY}},getter:{x:function(){return this._translateX||0},y:function(){return this._translateY||0},scale:function(){var t=(this.scale||"1,1").split(",");if(t[0]){t[0]=parseFloat(t[0])}if(t[1]){t[1]=parseFloat(t[1])}return t[0]===t[1]?t[0]:t},rotate3d:function(){var t=(this.rotate3d||"0,0,0,0deg").split(",");for(var e=0;e<=3;++e){if(t[e]){t[e]=parseFloat(t[e])}}if(t[3]){t[3]=b(t[3],"deg")}return t}},parse:function(t){var e=this;t.replace(/([a-zA-Z0-9]+)\((.*?)\)/g,function(t,n,i){e.setFromString(n,i)})},toString:function(t){var e=[];for(var i in this){if(this.hasOwnProperty(i)){if(!n.transform3d&&(i==="rotateX"||i==="rotateY"||i==="perspective"||i==="transformOrigin")){continue}if(i[0]!=="_"){if(t&&i==="scale"){e.push(i+"3d("+this[i]+",1)")}else if(t&&i==="translate"){e.push(i+"3d("+this[i]+",0)")}else{e.push(i+"("+this[i]+")")}}}}return e.join(" ")}};function c(t,e,n){if(e===true){t.queue(n)}else if(e){t.queue(e,n)}else{t.each(function(){n.call(this)})}}function l(e){var i=[];t.each(e,function(e){e=t.camelCase(e);e=t.transit.propertyMap[e]||t.cssProps[e]||e;e=h(e);if(n[e])e=h(n[e]);if(t.inArray(e,i)===-1){i.push(e)}});return i}function d(e,n,i,r){var s=l(e);if(t.cssEase[i]){i=t.cssEase[i]}var a=""+y(n)+" "+i;if(parseInt(r,10)>0){a+=" "+y(r)}var o=[];t.each(s,function(t,e){o.push(e+" "+a)});return o.join(", ")}t.fn.transition=t.fn.transit=function(e,i,r,s){var a=this;var u=0;var f=true;var l=t.extend(true,{},e);if(typeof i==="function"){s=i;i=undefined}if(typeof i==="object"){r=i.easing;u=i.delay||0;f=typeof i.queue==="undefined"?true:i.queue;s=i.complete;i=i.duration}if(typeof r==="function"){s=r;r=undefined}if(typeof l.easing!=="undefined"){r=l.easing;delete l.easing}if(typeof l.duration!=="undefined"){i=l.duration;delete l.duration}if(typeof l.complete!=="undefined"){s=l.complete;delete l.complete}if(typeof l.queue!=="undefined"){f=l.queue;delete l.queue}if(typeof l.delay!=="undefined"){u=l.delay;delete l.delay}if(typeof i==="undefined"){i=t.fx.speeds._default}if(typeof r==="undefined"){r=t.cssEase._default}i=y(i);var p=d(l,i,r,u);var h=t.transit.enabled&&n.transition;var b=h?parseInt(i,10)+parseInt(u,10):0;if(b===0){var g=function(t){a.css(l);if(s){s.apply(a)}if(t){t()}};c(a,f,g);return a}var m={};var v=function(e){var i=false;var r=function(){if(i){a.unbind(o,r)}if(b>0){a.each(function(){this.style[n.transition]=m[this]||null})}if(typeof s==="function"){s.apply(a)}if(typeof e==="function"){e()}};if(b>0&&o&&t.transit.useTransitionEnd){i=true;a.bind(o,r)}else{window.setTimeout(r,b)}a.each(function(){if(b>0){this.style[n.transition]=p}t(this).css(l)})};var z=function(t){this.offsetWidth;v(t)};c(a,f,z);return this};function p(e,i){if(!i){t.cssNumber[e]=true}t.transit.propertyMap[e]=n.transform;t.cssHooks[e]={get:function(n){var i=t(n).css("transit:transform");return i.get(e)},set:function(n,i){var r=t(n).css("transit:transform");r.setFromString(e,i);t(n).css({"transit:transform":r})}}}function h(t){return t.replace(/([A-Z])/g,function(t){return"-"+t.toLowerCase()})}function b(t,e){if(typeof t==="string"&&!t.match(/^[\-0-9\.]+$/)){return t}else{return""+t+e}}function y(e){var n=e;if(typeof n==="string"&&!n.match(/^[\-0-9\.]+/)){n=t.fx.speeds[n]||t.fx.speeds._default}return b(n,"ms")}t.transit.getTransitionValue=d;return t});
(function($) {
    /**
     * 让 jQuery 支持 Blob 类型
     */
    $.ajaxTransport('+*', function(options, originalOptions, jqXHR) {

        if (window.FormData && ((options.dataType &&
            (options.dataType === 'blob' || options.dataType === 'arraybuffer')) ||
            (options.data && ((window.Blob && options.data instanceof Blob) ||
                (window.ArrayBuffer && options.data instanceof ArrayBuffer)))
        )) {
            return {
                /**
                 * Return a transport capable of sending and/or receiving blobs - in this case, we instantiate
                 * a new XMLHttpRequest and use it to actually perform the request, and funnel the result back
                 * into the jquery complete callback (such as the success function, done blocks, etc.)
                 *
                 * @param headers
                 * @param completeCallback
                 */
                send: function(headers, completeCallback) {
                    var xhr = new XMLHttpRequest(),
                        url = options.url || window.location.href,
                        type = options.type || 'GET',
                        dataType = options.dataType || 'text',
                        data = options.data || null,
                        async = options.async || true,
                        key;

                    xhr.addEventListener('load', function() {
                        var response = {},
                            status, isSuccess;

                        isSuccess = xhr.status >= 200 && xhr.status < 300 || xhr.status === 304;

                        if (isSuccess) {
                            response[dataType] = xhr.response;
                        } else {
                            // In case an error occured we assume that the response body contains
                            // text data - so let's convert the binary data to a string which we can
                            // pass to the complete callback.
                            response.text = String.fromCharCode.apply(null, new Uint8Array(xhr.response));
                        }

                        completeCallback(xhr.status, xhr.statusText, response, xhr.getAllResponseHeaders());
                    });

                    xhr.open(type, url, async);
                    xhr.responseType = dataType;

                    for (key in headers) {
                        if (headers.hasOwnProperty(key)) xhr.setRequestHeader(key, headers[key]);
                    }
                    xhr.send(data);
                },
                abort: function() {
                    jqXHR.abort();
                }
            };
        }
    });
})(window.jQuery);
/*
 Copyright (c) 2012 Gildas Lormeau. All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 1. Redistributions of source code must retain the above copyright notice,
 this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in
 the documentation and/or other materials provided with the distribution.

 3. The names of the authors may not be used to endorse or promote products
 derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
 INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

(function(obj) {

	var ERR_BAD_FORMAT = "File format is not recognized.";
	var ERR_ENCRYPTED = "File contains encrypted entry.";
	var ERR_ZIP64 = "File is using Zip64 (4gb+ file size).";
	var ERR_READ = "Error while reading zip file.";
	var ERR_WRITE = "Error while writing zip file.";
	var ERR_WRITE_DATA = "Error while writing file data.";
	var ERR_READ_DATA = "Error while reading file data.";
	var ERR_DUPLICATED_NAME = "File already exists.";
	var ERR_HTTP_RANGE = "HTTP Range not supported.";
	var CHUNK_SIZE = 512 * 1024;

	var INFLATE_JS = "inflate.js";
	var DEFLATE_JS = "deflate.js";

	var appendABViewSupported;
	try {
		appendABViewSupported = new Blob([ getDataHelper(0).view ]).size == 0;
	} catch (e) {
	}	

	function Crc32() {
		var crc = -1, that = this;
		that.append = function(data) {
			var offset, table = that.table;
			for (offset = 0; offset < data.length; offset++)
				crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
		};
		that.get = function() {
			return ~crc;
		};
	}
	Crc32.prototype.table = (function() {
		var i, j, t, table = [];
		for (i = 0; i < 256; i++) {
			t = i;
			for (j = 0; j < 8; j++)
				if (t & 1)
					t = (t >>> 1) ^ 0xEDB88320;
				else
					t = t >>> 1;
			table[i] = t;
		}
		return table;
	})();

	function blobSlice(blob, index, length) {
		if (blob.slice)
			return blob.slice(index, index + length);
		else if (blob.webkitSlice)
			return blob.webkitSlice(index, index + length);
		else if (blob.mozSlice)
			return blob.mozSlice(index, index + length);
		else if (blob.msSlice)
			return blob.msSlice(index, index + length);
	}

	function getDataHelper(byteLength, bytes) {
		var dataBuffer, dataArray;
		dataBuffer = new ArrayBuffer(byteLength);
		dataArray = new Uint8Array(dataBuffer);
		if (bytes)
			dataArray.set(bytes, 0);
		return {
			buffer : dataBuffer,
			array : dataArray,
			view : new DataView(dataBuffer)
		};
	}

	// Readers
	function Reader() {
	}

	function TextReader(text) {
		var that = this, blobReader;

		function init(callback, onerror) {
			var blob = new Blob([ text ], {
				type : "text/plain"
			});
			blobReader = new BlobReader(blob);
			blobReader.init(function() {
				that.size = blobReader.size;
				callback();
			}, onerror);
		}

		function readUint8Array(index, length, callback, onerror) {
			blobReader.readUint8Array(index, length, callback, onerror);
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	TextReader.prototype = new Reader();
	TextReader.prototype.constructor = TextReader;

	function Data64URIReader(dataURI) {
		var that = this, dataStart;

		function init(callback) {
			var dataEnd = dataURI.length;
			while (dataURI.charAt(dataEnd - 1) == "=")
				dataEnd--;
			dataStart = dataURI.indexOf(",") + 1;
			that.size = Math.floor((dataEnd - dataStart) * 0.75);
			callback();
		}

		function readUint8Array(index, length, callback) {
			var i, data = getDataHelper(length);
			var start = Math.floor(index / 3) * 4;
			var end = Math.ceil((index + length) / 3) * 4;
			var bytes = obj.atob(dataURI.substring(start + dataStart, end + dataStart));
			var delta = index - Math.floor(start / 4) * 3;
			for (i = delta; i < delta + length; i++)
				data.array[i - delta] = bytes.charCodeAt(i);
			callback(data.array);
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	Data64URIReader.prototype = new Reader();
	Data64URIReader.prototype.constructor = Data64URIReader;

	function BlobReader(blob) {
		var that = this;

		function init(callback) {
			this.size = blob.size;
			callback();
		}

		function readUint8Array(index, length, callback, onerror) {
			var reader = new FileReader();
			reader.onload = function(e) {
				callback(new Uint8Array(e.target.result));
			};
			reader.onerror = onerror;
			reader.readAsArrayBuffer(blobSlice(blob, index, length));
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	BlobReader.prototype = new Reader();
	BlobReader.prototype.constructor = BlobReader;

	function HttpReader(url) {
		var that = this;

		function getData(callback, onerror) {
			var request;
			if (!that.data) {
				request = new XMLHttpRequest();
				request.addEventListener("load", function() {
					if (!that.size)
						that.size = Number(request.getResponseHeader("Content-Length"));
					that.data = new Uint8Array(request.response);
					callback();
				}, false);
				request.addEventListener("error", onerror, false);
				request.open("GET", url);
				request.responseType = "arraybuffer";
				request.send();
			} else
				callback();
		}

		function init(callback, onerror) {
			var request = new XMLHttpRequest();
			request.addEventListener("load", function() {
				that.size = Number(request.getResponseHeader("Content-Length"));
				callback();
			}, false);
			request.addEventListener("error", onerror, false);
			request.open("HEAD", url);
			request.send();
		}

		function readUint8Array(index, length, callback, onerror) {
			getData(function() {
				callback(new Uint8Array(that.data.subarray(index, index + length)));
			}, onerror);
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	HttpReader.prototype = new Reader();
	HttpReader.prototype.constructor = HttpReader;

	function HttpRangeReader(url) {
		var that = this;

		function init(callback, onerror) {
			var request = new XMLHttpRequest();
			request.addEventListener("load", function() {
				that.size = Number(request.getResponseHeader("Content-Length"));
				if (request.getResponseHeader("Accept-Ranges") == "bytes")
					callback();
				else
					onerror(ERR_HTTP_RANGE);
			}, false);
			request.addEventListener("error", onerror, false);
			request.open("HEAD", url);
			request.send();
		}

		function readArrayBuffer(index, length, callback, onerror) {
			var request = new XMLHttpRequest();
			request.open("GET", url);
			request.responseType = "arraybuffer";
			request.setRequestHeader("Range", "bytes=" + index + "-" + (index + length - 1));
			request.addEventListener("load", function() {
				callback(request.response);
			}, false);
			request.addEventListener("error", onerror, false);
			request.send();
		}

		function readUint8Array(index, length, callback, onerror) {
			readArrayBuffer(index, length, function(arraybuffer) {
				callback(new Uint8Array(arraybuffer));
			}, onerror);
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	HttpRangeReader.prototype = new Reader();
	HttpRangeReader.prototype.constructor = HttpRangeReader;

	// Writers

	function Writer() {
	}
	Writer.prototype.getData = function(callback) {
		callback(this.data);
	};

	function TextWriter() {
		var that = this, blob;

		function init(callback) {
			blob = new Blob([], {
				type : "text/plain"
			});
			callback();
		}

		function writeUint8Array(array, callback) {
			blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
				type : "text/plain"
			});
			callback();
		}

		function getData(callback, onerror) {
			var reader = new FileReader();
			reader.onload = function(e) {
				callback(e.target.result);
			};
			reader.onerror = onerror;
			reader.readAsText(blob);
		}

		that.init = init;
		that.writeUint8Array = writeUint8Array;
		that.getData = getData;
	}
	TextWriter.prototype = new Writer();
	TextWriter.prototype.constructor = TextWriter;

	function Data64URIWriter(contentType) {
		var that = this, data = "", pending = "";

		function init(callback) {
			data += "data:" + (contentType || "") + ";base64,";
			callback();
		}

		function writeUint8Array(array, callback) {
			var i, delta = pending.length, dataString = pending;
			pending = "";
			for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++)
				dataString += String.fromCharCode(array[i]);
			for (; i < array.length; i++)
				pending += String.fromCharCode(array[i]);
			if (dataString.length > 2)
				data += obj.btoa(dataString);
			else
				pending = dataString;
			callback();
		}

		function getData(callback) {
			callback(data + obj.btoa(pending));
		}

		that.init = init;
		that.writeUint8Array = writeUint8Array;
		that.getData = getData;
	}
	Data64URIWriter.prototype = new Writer();
	Data64URIWriter.prototype.constructor = Data64URIWriter;

	function FileWriter(fileEntry, contentType) {
		var writer, that = this;

		function init(callback, onerror) {
			fileEntry.createWriter(function(fileWriter) {
				writer = fileWriter;
				callback();
			}, onerror);
		}

		function writeUint8Array(array, callback, onerror) {
			var blob = new Blob([ appendABViewSupported ? array : array.buffer ], {
				type : contentType
			});
			writer.onwrite = function() {
				writer.onwrite = null;
				callback();
			};
			writer.onerror = onerror;
			writer.write(blob);
		}

		function getData(callback) {
			fileEntry.file(callback);
		}

		that.init = init;
		that.writeUint8Array = writeUint8Array;
		that.getData = getData;
	}
	FileWriter.prototype = new Writer();
	FileWriter.prototype.constructor = FileWriter;

	function BlobWriter(contentType) {
		var blob, that = this;

		function init(callback) {
			blob = new Blob([], {
				type : contentType
			});
			callback();
		}

		function writeUint8Array(array, callback) {
			blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
				type : contentType
			});
			callback();
		}

		function getData(callback) {
			callback(blob);
		}

		that.init = init;
		that.writeUint8Array = writeUint8Array;
		that.getData = getData;
	}
	BlobWriter.prototype = new Writer();
	BlobWriter.prototype.constructor = BlobWriter;

	// inflate/deflate core functions

	function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
		var chunkIndex = 0, index, outputSize;

		function onflush() {
			worker.removeEventListener("message", onmessage, false);
			onend(outputSize);
		}

		function onmessage(event) {
			var message = event.data, data = message.data;

			if (message.onappend) {
				outputSize += data.length;
				writer.writeUint8Array(data, function() {
					onappend(false, data);
					step();
				}, onwriteerror);
			}
			if (message.onflush)
				if (data) {
					outputSize += data.length;
					writer.writeUint8Array(data, function() {
						onappend(false, data);
						onflush();
					}, onwriteerror);
				} else
					onflush();
			if (message.progress && onprogress)
				onprogress(index + message.current, size);
		}

		function step() {
			index = chunkIndex * CHUNK_SIZE;
			if (index < size)
				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
					worker.postMessage({
						append : true,
						data : array
					});
					chunkIndex++;
					if (onprogress)
						onprogress(index, size);
					onappend(true, array);
				}, onreaderror);
			else
				worker.postMessage({
					flush : true
				});
		}

		outputSize = 0;
		worker.addEventListener("message", onmessage, false);
		step();
	}

	function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
		var chunkIndex = 0, index, outputSize = 0;

		function step() {
			var outputData;
			index = chunkIndex * CHUNK_SIZE;
			if (index < size)
				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) {
					var outputData = process.append(inputData, function() {
						if (onprogress)
							onprogress(offset + index, size);
					});
					outputSize += outputData.length;
					onappend(true, inputData);
					writer.writeUint8Array(outputData, function() {
						onappend(false, outputData);
						chunkIndex++;
						setTimeout(step, 1);
					}, onwriteerror);
					if (onprogress)
						onprogress(index, size);
				}, onreaderror);
			else {
				outputData = process.flush();
				if (outputData) {
					outputSize += outputData.length;
					writer.writeUint8Array(outputData, function() {
						onappend(false, outputData);
						onend(outputSize);
					}, onwriteerror);
				} else
					onend(outputSize);
			}
		}

		step();
	}

	function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
		var worker, crc32 = new Crc32();

		function oninflateappend(sending, array) {
			if (computeCrc32 && !sending)
				crc32.append(array);
		}

		function oninflateend(outputSize) {
			onend(outputSize, crc32.get());
		}

		if (obj.zip.useWebWorkers) {
			worker = new Worker(obj.zip.inflateJSPath || INFLATE_JS);//INFLATE_JS
			launchWorkerProcess(worker, reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
		} else
			launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
		return worker;
	}

	function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) {
		var worker, crc32 = new Crc32();

		function ondeflateappend(sending, array) {
			if (sending)
				crc32.append(array);
		}

		function ondeflateend(outputSize) {
			onend(outputSize, crc32.get());
		}

		function onmessage() {
			worker.removeEventListener("message", onmessage, false);
			launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
		}

		if (obj.zip.useWebWorkers) {
			worker = new Worker(obj.zip.workerScriptsPath + DEFLATE_JS);
			worker.addEventListener("message", onmessage, false);
			worker.postMessage({
				init : true,
				level : level
			});
		} else
			launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
		return worker;
	}

	function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
		var chunkIndex = 0, crc32 = new Crc32();

		function step() {
			var index = chunkIndex * CHUNK_SIZE;
			if (index < size)
				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
					if (computeCrc32)
						crc32.append(array);
					if (onprogress)
						onprogress(index, size, array);
					writer.writeUint8Array(array, function() {
						chunkIndex++;
						step();
					}, onwriteerror);
				}, onreaderror);
			else
				onend(size, crc32.get());
		}

		step();
	}

	// ZipReader

	function decodeASCII(str) {
		var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB',
				'\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9',
				'\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1',
				'\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6',
				'\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3',
				'\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE',
				'\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE',
				'\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7',
				'\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ];
		for (i = 0; i < str.length; i++) {
			charCode = str.charCodeAt(i) & 0xFF;
			if (charCode > 127)
				out += extendedASCII[charCode - 128];
			else
				out += String.fromCharCode(charCode);
		}
		return out;
	}

	function decodeUTF8(str_data) {
		var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0;

		str_data += '';

		while (i < str_data.length) {
			c1 = str_data.charCodeAt(i);
			if (c1 < 128) {
				tmp_arr[ac++] = String.fromCharCode(c1);
				i++;
			} else if (c1 > 191 && c1 < 224) {
				c2 = str_data.charCodeAt(i + 1);
				tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
				i += 2;
			} else {
				c2 = str_data.charCodeAt(i + 1);
				c3 = str_data.charCodeAt(i + 2);
				tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}
		}

		return tmp_arr.join('');
	}

	function getString(bytes) {
		var i, str = "";
		for (i = 0; i < bytes.length; i++)
			str += String.fromCharCode(bytes[i]);
		return str;
	}

	function getDate(timeRaw) {
		var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff;
		try {
			return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5,
					(time & 0x001F) * 2, 0);
		} catch (e) {
		}
	}

	function readCommonHeader(entry, data, index, centralDirectory, onerror) {
		entry.version = data.view.getUint16(index, true);
		entry.bitFlag = data.view.getUint16(index + 2, true);
		entry.compressionMethod = data.view.getUint16(index + 4, true);
		entry.lastModDateRaw = data.view.getUint32(index + 6, true);
		entry.lastModDate = getDate(entry.lastModDateRaw);
		if ((entry.bitFlag & 0x01) === 0x01) {
			onerror(ERR_ENCRYPTED);
			return;
		}
		if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) {
			entry.crc32 = data.view.getUint32(index + 10, true);
			entry.compressedSize = data.view.getUint32(index + 14, true);
			entry.uncompressedSize = data.view.getUint32(index + 18, true);
		}
		if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) {
			onerror(ERR_ZIP64);
			return;
		}
		entry.filenameLength = data.view.getUint16(index + 22, true);
		entry.extraFieldLength = data.view.getUint16(index + 24, true);
	}

	function createZipReader(reader, onerror) {
		function Entry() {
		}

		Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) {
			var that = this, worker;

			function terminate(callback, param) {
				if (worker)
					worker.terminate();
				worker = null;
				if (callback)
					callback(param);
			}

			function testCrc32(crc32) {
				var dataCrc32 = getDataHelper(4);
				dataCrc32.view.setUint32(0, crc32);
				return that.crc32 == dataCrc32.view.getUint32(0);
			}

			function getWriterData(uncompressedSize, crc32) {
				if (checkCrc32 && !testCrc32(crc32))
					onreaderror();
				else
					writer.getData(function(data) {
						terminate(onend, data);
					});
			}

			function onreaderror() {
				terminate(onerror, ERR_READ_DATA);
			}

			function onwriteerror() {
				terminate(onerror, ERR_WRITE_DATA);
			}

			reader.readUint8Array(that.offset, 30, function(bytes) {
				var data = getDataHelper(bytes.length, bytes), dataOffset;
				if (data.view.getUint32(0) != 0x504b0304) {
					onerror(ERR_BAD_FORMAT);
					return;
				}
				readCommonHeader(that, data, 4, false, function(error) {
					onerror(error);
					return;
				});
				dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength;
				writer.init(function() {
					if (that.compressionMethod === 0)
						copy(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
					else
						worker = inflate(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
				}, onwriteerror);
			}, onreaderror);
		};

		function seekEOCDR(offset, entriesCallback) {
			reader.readUint8Array(reader.size - offset, offset, function(bytes) {
				var dataView = getDataHelper(bytes.length, bytes).view;
				try{
					if (dataView.getUint32(0) != 0x504b0506) {
						seekEOCDR(offset + 1, entriesCallback);
					} else {
						entriesCallback(dataView);
					}
				}catch(e){
					onerror(ERR_READ);
				}

			}, function() {
				onerror(ERR_READ);
			});
		}

		return {
			getEntries : function(callback) {
				if (reader.size < 22) {
					onerror(ERR_BAD_FORMAT);
					return;
				}
				// look for End of central directory record
				seekEOCDR(22, function(dataView) {
					var datalength, fileslength;
					datalength = dataView.getUint32(16, true);
					fileslength = dataView.getUint16(8, true);
					reader.readUint8Array(datalength, reader.size - datalength, function(bytes) {
						var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes);
						for (i = 0; i < fileslength; i++) {
							entry = new Entry();
							if (data.view.getUint32(index) != 0x504b0102) {
								onerror(ERR_BAD_FORMAT);
								return;
							}
							readCommonHeader(entry, data, index + 6, true, function(error) {
								onerror(error);
								return;
							});
							entry.commentLength = data.view.getUint16(index + 32, true);
							entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10);
							entry.offset = data.view.getUint32(index + 42, true);
							filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength));
							entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename);
							if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/")
								entry.directory = true;
							comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46
									+ entry.filenameLength + entry.extraFieldLength + entry.commentLength));
							entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment);
							entries.push(entry);
							index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength;
						}
						callback(entries);
					}, function() {
						onerror(ERR_READ);
					});
				});
			},
			close : function(callback) {
				if (callback)
					callback();
			}
		};
	}

	// ZipWriter

	function encodeUTF8(string) {
		var n, c1, enc, utftext = [], start = 0, end = 0, stringl = string.length;
		for (n = 0; n < stringl; n++) {
			c1 = string.charCodeAt(n);
			enc = null;
			if (c1 < 128)
				end++;
			else if (c1 > 127 && c1 < 2048)
				enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128);
			else
				enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128);
			if (enc != null) {
				if (end > start)
					utftext += string.slice(start, end);
				utftext += enc;
				start = end = n + 1;
			}
		}
		if (end > start)
			utftext += string.slice(start, stringl);
		return utftext;
	}

	function getBytes(str) {
		var i, array = [];
		for (i = 0; i < str.length; i++)
			array.push(str.charCodeAt(i));
		return array;
	}

	function createZipWriter(writer, onerror, dontDeflate) {
		var worker, files = [], filenames = [], datalength = 0;

		function terminate(callback, message) {
			if (worker)
				worker.terminate();
			worker = null;
			if (callback)
				callback(message);
		}

		function onwriteerror() {
			terminate(onerror, ERR_WRITE);
		}

		function onreaderror() {
			terminate(onerror, ERR_READ_DATA);
		}

		return {
			add : function(name, reader, onend, onprogress, options) {
				var header, filename, date;

				function writeHeader(callback) {
					var data;
					date = options.lastModDate || new Date();
					header = getDataHelper(26);
					files[name] = {
						headerArray : header.array,
						directory : options.directory,
						filename : filename,
						offset : datalength,
						comment : getBytes(encodeUTF8(options.comment || ""))
					};
					header.view.setUint32(0, 0x14000808);
					if (options.version)
						header.view.setUint8(0, options.version);
					if (!dontDeflate && options.level != 0 && !options.directory)
						header.view.setUint16(4, 0x0800);
					header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
					header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
					header.view.setUint16(22, filename.length, true);
					data = getDataHelper(30 + filename.length);
					data.view.setUint32(0, 0x504b0304);
					data.array.set(header.array, 4);
					data.array.set(filename, 30);
					datalength += data.array.length;
					writer.writeUint8Array(data.array, callback, onwriteerror);
				}

				function writeFooter(compressedLength, crc32) {
					var footer = getDataHelper(16);
					datalength += compressedLength || 0;
					footer.view.setUint32(0, 0x504b0708);
					if (typeof crc32 != "undefined") {
						header.view.setUint32(10, crc32, true);
						footer.view.setUint32(4, crc32, true);
					}
					if (reader) {
						footer.view.setUint32(8, compressedLength, true);
						header.view.setUint32(14, compressedLength, true);
						footer.view.setUint32(12, reader.size, true);
						header.view.setUint32(18, reader.size, true);
					}
					writer.writeUint8Array(footer.array, function() {
						datalength += 16;
						terminate(onend);
					}, onwriteerror);
				}

				function writeFile() {
					options = options || {};
					name = name.trim();
					if (options.directory && name.charAt(name.length - 1) != "/")
						name += "/";
					if (files[name])
						throw ERR_DUPLICATED_NAME;
					filename = getBytes(encodeUTF8(name));
					filenames.push(name);
					writeHeader(function() {
						if (reader)
							if (dontDeflate || options.level == 0)
								copy(reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror);
							else
								worker = deflate(reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror);
						else
							writeFooter();
					}, onwriteerror);
				}

				if (reader)
					reader.init(writeFile, onreaderror);
				else
					writeFile();
			},
			close : function(callback) {
				var data, length = 0, index = 0;
				filenames.forEach(function(name) {
					var file = files[name];
					length += 46 + file.filename.length + file.comment.length;
				});
				data = getDataHelper(length + 22);
				filenames.forEach(function(name) {
					var file = files[name];
					data.view.setUint32(index, 0x504b0102);
					data.view.setUint16(index + 4, 0x1400);
					data.array.set(file.headerArray, index + 6);
					data.view.setUint16(index + 32, file.comment.length, true);
					if (file.directory)
						data.view.setUint8(index + 38, 0x10);
					data.view.setUint32(index + 42, file.offset, true);
					data.array.set(file.filename, index + 46);
					data.array.set(file.comment, index + 46 + file.filename.length);
					index += 46 + file.filename.length + file.comment.length;
				});
				data.view.setUint32(index, 0x504b0506);
				data.view.setUint16(index + 8, filenames.length, true);
				data.view.setUint16(index + 10, filenames.length, true);
				data.view.setUint32(index + 12, length, true);
				data.view.setUint32(index + 16, datalength, true);
				writer.writeUint8Array(data.array, function() {
					terminate(function() {
						writer.getData(callback);
					});
				}, onwriteerror);
			}
		};
	}

	obj.zip = {
		Reader : Reader,
		Writer : Writer,
		BlobReader : BlobReader,
		HttpReader : HttpReader,
		HttpRangeReader : HttpRangeReader,
		Data64URIReader : Data64URIReader,
		TextReader : TextReader,
		BlobWriter : BlobWriter,
		FileWriter : FileWriter,
		Data64URIWriter : Data64URIWriter,
		TextWriter : TextWriter,
		createReader : function(reader, callback, onerror) {
			reader.init(function() {
				callback(createZipReader(reader, onerror));
			}, onerror);
		},
		createWriter : function(writer, callback, onerror, dontDeflate) {
			writer.init(function() {
				callback(createZipWriter(writer, onerror, dontDeflate));
			}, onerror);
		},
		workerScriptsPath : "",
		inflateJSPath:"",
		useWebWorkers : true
	};

})(this);

/*!
 * ZeroClipboard
 * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface.
 * Copyright (c) 2014 Jon Rohan, James M. Greene
 * Licensed MIT
 * http://zeroclipboard.org/
 * v2.1.6
 */
!function(a,b){"use strict";var c,d,e=a,f=e.document,g=e.navigator,h=e.setTimeout,i=e.encodeURIComponent,j=e.ActiveXObject,k=e.Error,l=e.Number.parseInt||e.parseInt,m=e.Number.parseFloat||e.parseFloat,n=e.Number.isNaN||e.isNaN,o=e.Math.round,p=e.Date.now,q=e.Object.keys,r=e.Object.defineProperty,s=e.Object.prototype.hasOwnProperty,t=e.Array.prototype.slice,u=function(){var a=function(a){return a};if("function"==typeof e.wrap&&"function"==typeof e.unwrap)try{var b=f.createElement("div"),c=e.unwrap(b);1===b.nodeType&&c&&1===c.nodeType&&(a=e.unwrap)}catch(d){}return a}(),v=function(a){return t.call(a,0)},w=function(){var a,c,d,e,f,g,h=v(arguments),i=h[0]||{};for(a=1,c=h.length;c>a;a++)if(null!=(d=h[a]))for(e in d)s.call(d,e)&&(f=i[e],g=d[e],i!==g&&g!==b&&(i[e]=g));return i},x=function(a){var b,c,d,e;if("object"!=typeof a||null==a)b=a;else if("number"==typeof a.length)for(b=[],c=0,d=a.length;d>c;c++)s.call(a,c)&&(b[c]=x(a[c]));else{b={};for(e in a)s.call(a,e)&&(b[e]=x(a[e]))}return b},y=function(a,b){for(var c={},d=0,e=b.length;e>d;d++)b[d]in a&&(c[b[d]]=a[b[d]]);return c},z=function(a,b){var c={};for(var d in a)-1===b.indexOf(d)&&(c[d]=a[d]);return c},A=function(a){if(a)for(var b in a)s.call(a,b)&&delete a[b];return a},B=function(a,b){if(a&&1===a.nodeType&&a.ownerDocument&&b&&(1===b.nodeType&&b.ownerDocument&&b.ownerDocument===a.ownerDocument||9===b.nodeType&&!b.ownerDocument&&b===a.ownerDocument))do{if(a===b)return!0;a=a.parentNode}while(a);return!1},C=function(a){var b;return"string"==typeof a&&a&&(b=a.split("#")[0].split("?")[0],b=a.slice(0,a.lastIndexOf("/")+1)),b},D=function(a){var b,c;return"string"==typeof a&&a&&(c=a.match(/^(?:|[^:@]*@|.+\)@(?=http[s]?|file)|.+?\s+(?: at |@)(?:[^:\(]+ )*[\(]?)((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]?b=c[1]:(c=a.match(/\)@((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]&&(b=c[1]))),b},E=function(){var a,b;try{throw new k}catch(c){b=c}return b&&(a=b.sourceURL||b.fileName||D(b.stack)),a},F=function(){var a,c,d;if(f.currentScript&&(a=f.currentScript.src))return a;if(c=f.getElementsByTagName("script"),1===c.length)return c[0].src||b;if("readyState"in c[0])for(d=c.length;d--;)if("interactive"===c[d].readyState&&(a=c[d].src))return a;return"loading"===f.readyState&&(a=c[c.length-1].src)?a:(a=E())?a:b},G=function(){var a,c,d,e=f.getElementsByTagName("script");for(a=e.length;a--;){if(!(d=e[a].src)){c=null;break}if(d=C(d),null==c)c=d;else if(c!==d){c=null;break}}return c||b},H=function(){var a=C(F())||G()||"";return a+"ZeroClipboard.swf"},I={bridge:null,version:"0.0.0",pluginType:"unknown",disabled:null,outdated:null,unavailable:null,deactivated:null,overdue:null,ready:null},J="11.0.0",K={},L={},M=null,N={ready:"Flash communication is established",error:{"flash-disabled":"Flash is disabled or not installed","flash-outdated":"Flash is too outdated to support ZeroClipboard","flash-unavailable":"Flash is unable to communicate bidirectionally with JavaScript","flash-deactivated":"Flash is too outdated for your browser and/or is configured as click-to-activate","flash-overdue":"Flash communication was established but NOT within the acceptable time limit"}},O={swfPath:H(),trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceEnhancedClipboard:!1,flashLoadTimeout:3e4,autoActivate:!0,bubbleEvents:!0,containerId:"global-zeroclipboard-html-bridge",containerClass:"global-zeroclipboard-container",swfObjectId:"global-zeroclipboard-flash-bridge",hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",forceHandCursor:!1,title:null,zIndex:999999999},P=function(a){if("object"==typeof a&&null!==a)for(var b in a)if(s.call(a,b))if(/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(b))O[b]=a[b];else if(null==I.bridge)if("containerId"===b||"swfObjectId"===b){if(!cb(a[b]))throw new Error("The specified `"+b+"` value is not valid as an HTML4 Element ID");O[b]=a[b]}else O[b]=a[b];{if("string"!=typeof a||!a)return x(O);if(s.call(O,a))return O[a]}},Q=function(){return{browser:y(g,["userAgent","platform","appName"]),flash:z(I,["bridge"]),zeroclipboard:{version:Fb.version,config:Fb.config()}}},R=function(){return!!(I.disabled||I.outdated||I.unavailable||I.deactivated)},S=function(a,b){var c,d,e,f={};if("string"==typeof a&&a)e=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.on(c,a[c]);if(e&&e.length){for(c=0,d=e.length;d>c;c++)a=e[c].replace(/^on/,""),f[a]=!0,K[a]||(K[a]=[]),K[a].push(b);if(f.ready&&I.ready&&Fb.emit({type:"ready"}),f.error){var g=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=g.length;d>c;c++)if(I[g[c]]===!0){Fb.emit({type:"error",name:"flash-"+g[c]});break}}}return Fb},T=function(a,b){var c,d,e,f,g;if(0===arguments.length)f=q(K);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=K[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return Fb},U=function(a){var b;return b="string"==typeof a&&a?x(K[a])||null:x(K)},V=function(a){var b,c,d;return a=db(a),a&&!jb(a)?"ready"===a.type&&I.overdue===!0?Fb.emit({type:"error",name:"flash-overdue"}):(b=w({},a),ib.call(this,b),"copy"===a.type&&(d=pb(L),c=d.data,M=d.formatMap),c):void 0},W=function(){if("boolean"!=typeof I.ready&&(I.ready=!1),!Fb.isFlashUnusable()&&null===I.bridge){var a=O.flashLoadTimeout;"number"==typeof a&&a>=0&&h(function(){"boolean"!=typeof I.deactivated&&(I.deactivated=!0),I.deactivated===!0&&Fb.emit({type:"error",name:"flash-deactivated"})},a),I.overdue=!1,nb()}},X=function(){Fb.clearData(),Fb.blur(),Fb.emit("destroy"),ob(),Fb.off()},Y=function(a,b){var c;if("object"==typeof a&&a&&"undefined"==typeof b)c=a,Fb.clearData();else{if("string"!=typeof a||!a)return;c={},c[a]=b}for(var d in c)"string"==typeof d&&d&&s.call(c,d)&&"string"==typeof c[d]&&c[d]&&(L[d]=c[d])},Z=function(a){"undefined"==typeof a?(A(L),M=null):"string"==typeof a&&s.call(L,a)&&delete L[a]},$=function(a){return"undefined"==typeof a?x(L):"string"==typeof a&&s.call(L,a)?L[a]:void 0},_=function(a){if(a&&1===a.nodeType){c&&(xb(c,O.activeClass),c!==a&&xb(c,O.hoverClass)),c=a,wb(a,O.hoverClass);var b=a.getAttribute("title")||O.title;if("string"==typeof b&&b){var d=mb(I.bridge);d&&d.setAttribute("title",b)}var e=O.forceHandCursor===!0||"pointer"===yb(a,"cursor");Cb(e),Bb()}},ab=function(){var a=mb(I.bridge);a&&(a.removeAttribute("title"),a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.top="1px"),c&&(xb(c,O.hoverClass),xb(c,O.activeClass),c=null)},bb=function(){return c||null},cb=function(a){return"string"==typeof a&&a&&/^[A-Za-z][A-Za-z0-9_:\-\.]*$/.test(a)},db=function(a){var b;if("string"==typeof a&&a?(b=a,a={}):"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(b=a.type),b){!a.target&&/^(copy|aftercopy|_click)$/.test(b.toLowerCase())&&(a.target=d),w(a,{type:b.toLowerCase(),target:a.target||c||null,relatedTarget:a.relatedTarget||null,currentTarget:I&&I.bridge||null,timeStamp:a.timeStamp||p()||null});var e=N[a.type];return"error"===a.type&&a.name&&e&&(e=e[a.name]),e&&(a.message=e),"ready"===a.type&&w(a,{target:null,version:I.version}),"error"===a.type&&(/^flash-(disabled|outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{target:null,minimumVersion:J}),/^flash-(outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{version:I.version})),"copy"===a.type&&(a.clipboardData={setData:Fb.setData,clearData:Fb.clearData}),"aftercopy"===a.type&&(a=qb(a,M)),a.target&&!a.relatedTarget&&(a.relatedTarget=eb(a.target)),a=fb(a)}},eb=function(a){var b=a&&a.getAttribute&&a.getAttribute("data-clipboard-target");return b?f.getElementById(b):null},fb=function(a){if(a&&/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)){var c=a.target,d="_mouseover"===a.type&&a.relatedTarget?a.relatedTarget:b,g="_mouseout"===a.type&&a.relatedTarget?a.relatedTarget:b,h=Ab(c),i=e.screenLeft||e.screenX||0,j=e.screenTop||e.screenY||0,k=f.body.scrollLeft+f.documentElement.scrollLeft,l=f.body.scrollTop+f.documentElement.scrollTop,m=h.left+("number"==typeof a._stageX?a._stageX:0),n=h.top+("number"==typeof a._stageY?a._stageY:0),o=m-k,p=n-l,q=i+o,r=j+p,s="number"==typeof a.movementX?a.movementX:0,t="number"==typeof a.movementY?a.movementY:0;delete a._stageX,delete a._stageY,w(a,{srcElement:c,fromElement:d,toElement:g,screenX:q,screenY:r,pageX:m,pageY:n,clientX:o,clientY:p,x:o,y:p,movementX:s,movementY:t,offsetX:0,offsetY:0,layerX:0,layerY:0})}return a},gb=function(a){var b=a&&"string"==typeof a.type&&a.type||"";return!/^(?:(?:before)?copy|destroy)$/.test(b)},hb=function(a,b,c,d){d?h(function(){a.apply(b,c)},0):a.apply(b,c)},ib=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=K["*"]||[],d=K[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},jb=function(a){var b=a.target||c||null,e="swf"===a._source;delete a._source;var f=["flash-disabled","flash-outdated","flash-unavailable","flash-deactivated","flash-overdue"];switch(a.type){case"error":-1!==f.indexOf(a.name)&&w(I,{disabled:"flash-disabled"===a.name,outdated:"flash-outdated"===a.name,unavailable:"flash-unavailable"===a.name,deactivated:"flash-deactivated"===a.name,overdue:"flash-overdue"===a.name,ready:!1});break;case"ready":var g=I.deactivated===!0;w(I,{disabled:!1,outdated:!1,unavailable:!1,deactivated:!1,overdue:g,ready:!g});break;case"beforecopy":d=b;break;case"copy":var h,i,j=a.relatedTarget;!L["text/html"]&&!L["text/plain"]&&j&&(i=j.value||j.outerHTML||j.innerHTML)&&(h=j.value||j.textContent||j.innerText)?(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h),i!==h&&a.clipboardData.setData("text/html",i)):!L["text/plain"]&&a.target&&(h=a.target.getAttribute("data-clipboard-text"))&&(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h));break;case"aftercopy":Fb.clearData(),b&&b!==vb()&&b.focus&&b.focus();break;case"_mouseover":Fb.focus(b),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseenter",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseover"})));break;case"_mouseout":Fb.blur(),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseleave",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseout"})));break;case"_mousedown":wb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mouseup":xb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_click":d=null,O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mousemove":O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}))}return/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)?!0:void 0},kb=function(a){if(a&&"string"==typeof a.type&&a){var b,c=a.target||null,d=c&&c.ownerDocument||f,g={view:d.defaultView||e,canBubble:!0,cancelable:!0,detail:"click"===a.type?1:0,button:"number"==typeof a.which?a.which-1:"number"==typeof a.button?a.button:d.createEvent?0:1},h=w(g,a);c&&d.createEvent&&c.dispatchEvent&&(h=[h.type,h.canBubble,h.cancelable,h.view,h.detail,h.screenX,h.screenY,h.clientX,h.clientY,h.ctrlKey,h.altKey,h.shiftKey,h.metaKey,h.button,h.relatedTarget],b=d.createEvent("MouseEvents"),b.initMouseEvent&&(b.initMouseEvent.apply(b,h),b._source="js",c.dispatchEvent(b)))}},lb=function(){var a=f.createElement("div");return a.id=O.containerId,a.className=O.containerClass,a.style.position="absolute",a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.height="1px",a.style.zIndex=""+Db(O.zIndex),a},mb=function(a){for(var b=a&&a.parentNode;b&&"OBJECT"===b.nodeName&&b.parentNode;)b=b.parentNode;return b||null},nb=function(){var a,b=I.bridge,c=mb(b);if(!b){var d=ub(e.location.host,O),g="never"===d?"none":"all",h=sb(O),i=O.swfPath+rb(O.swfPath,O);c=lb();var j=f.createElement("div");c.appendChild(j),f.body.appendChild(c);var k=f.createElement("div"),l="activex"===I.pluginType;k.innerHTML='<object id="'+O.swfObjectId+'" name="'+O.swfObjectId+'" width="100%" height="100%" '+(l?'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"':'type="application/x-shockwave-flash" data="'+i+'"')+">"+(l?'<param name="movie" value="'+i+'"/>':"")+'<param name="allowScriptAccess" value="'+d+'"/><param name="allowNetworking" value="'+g+'"/><param name="menu" value="false"/><param name="wmode" value="transparent"/><param name="flashvars" value="'+h+'"/></object>',b=k.firstChild,k=null,u(b).ZeroClipboard=Fb,c.replaceChild(b,j)}return b||(b=f[O.swfObjectId],b&&(a=b.length)&&(b=b[a-1]),!b&&c&&(b=c.firstChild)),I.bridge=b||null,b},ob=function(){var a=I.bridge;if(a){var b=mb(a);b&&("activex"===I.pluginType&&"readyState"in a?(a.style.display="none",function c(){if(4===a.readyState){for(var d in a)"function"==typeof a[d]&&(a[d]=null);a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b)}else h(c,10)}()):(a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b))),I.ready=null,I.bridge=null,I.deactivated=null}},pb=function(a){var b={},c={};if("object"==typeof a&&a){for(var d in a)if(d&&s.call(a,d)&&"string"==typeof a[d]&&a[d])switch(d.toLowerCase()){case"text/plain":case"text":case"air:text":case"flash:text":b.text=a[d],c.text=d;break;case"text/html":case"html":case"air:html":case"flash:html":b.html=a[d],c.html=d;break;case"application/rtf":case"text/rtf":case"rtf":case"richtext":case"air:rtf":case"flash:rtf":b.rtf=a[d],c.rtf=d}return{data:b,formatMap:c}}},qb=function(a,b){if("object"!=typeof a||!a||"object"!=typeof b||!b)return a;var c={};for(var d in a)if(s.call(a,d)){if("success"!==d&&"data"!==d){c[d]=a[d];continue}c[d]={};var e=a[d];for(var f in e)f&&s.call(e,f)&&s.call(b,f)&&(c[d][b[f]]=e[f])}return c},rb=function(a,b){var c=null==b||b&&b.cacheBust===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+p():""},sb=function(a){var b,c,d,f,g="",h=[];if(a.trustedDomains&&("string"==typeof a.trustedDomains?f=[a.trustedDomains]:"object"==typeof a.trustedDomains&&"length"in a.trustedDomains&&(f=a.trustedDomains)),f&&f.length)for(b=0,c=f.length;c>b;b++)if(s.call(f,b)&&f[b]&&"string"==typeof f[b]){if(d=tb(f[b]),!d)continue;if("*"===d){h.length=0,h.push(d);break}h.push.apply(h,[d,"//"+d,e.location.protocol+"//"+d])}return h.length&&(g+="trustedOrigins="+i(h.join(","))),a.forceEnhancedClipboard===!0&&(g+=(g?"&":"")+"forceEnhancedClipboard=true"),"string"==typeof a.swfObjectId&&a.swfObjectId&&(g+=(g?"&":"")+"swfObjectId="+i(a.swfObjectId)),g},tb=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},ub=function(){var a=function(a){var b,c,d,e=[];if("string"==typeof a&&(a=[a]),"object"!=typeof a||!a||"number"!=typeof a.length)return e;for(b=0,c=a.length;c>b;b++)if(s.call(a,b)&&(d=tb(a[b]))){if("*"===d){e.length=0,e.push("*");break}-1===e.indexOf(d)&&e.push(d)}return e};return function(b,c){var d=tb(c.swfPath);null===d&&(d=b);var e=a(c.trustedDomains),f=e.length;if(f>0){if(1===f&&"*"===e[0])return"always";if(-1!==e.indexOf(b))return 1===f&&b===d?"sameDomain":"always"}return"never"}}(),vb=function(){try{return f.activeElement}catch(a){return null}},wb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)||a.classList.add(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},xb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)&&a.classList.remove(b),a;if("string"==typeof b&&b){var c=b.split(/\s+/);if(1===a.nodeType&&a.className){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}}return a},yb=function(a,b){var c=e.getComputedStyle(a,null).getPropertyValue(b);return"cursor"!==b||c&&"auto"!==c||"A"!==a.nodeName?c:"pointer"},zb=function(){var a,b,c,d=1;return"function"==typeof f.body.getBoundingClientRect&&(a=f.body.getBoundingClientRect(),b=a.right-a.left,c=f.body.offsetWidth,d=o(b/c*100)/100),d},Ab=function(a){var b={left:0,top:0,width:0,height:0};if(a.getBoundingClientRect){var c,d,g,h=a.getBoundingClientRect();"pageXOffset"in e&&"pageYOffset"in e?(c=e.pageXOffset,d=e.pageYOffset):(g=zb(),c=o(f.documentElement.scrollLeft/g),d=o(f.documentElement.scrollTop/g));var i=f.documentElement.clientLeft||0,j=f.documentElement.clientTop||0;b.left=h.left+c-i,b.top=h.top+d-j,b.width="width"in h?h.width:h.right-h.left,b.height="height"in h?h.height:h.bottom-h.top}return b},Bb=function(){var a;if(c&&(a=mb(I.bridge))){var b=Ab(c);w(a.style,{width:b.width+"px",height:b.height+"px",top:b.top+"px",left:b.left+"px",zIndex:""+Db(O.zIndex)})}},Cb=function(a){I.ready===!0&&(I.bridge&&"function"==typeof I.bridge.setHandCursor?I.bridge.setHandCursor(a):I.ready=!1)},Db=function(a){if(/^(?:auto|inherit)$/.test(a))return a;var b;return"number"!=typeof a||n(a)?"string"==typeof a&&(b=Db(l(a,10))):b=a,"number"==typeof b?b:"auto"},Eb=function(a){function b(a){var b=a.match(/[\d]+/g);return b.length=3,b.join(".")}function c(a){return!!a&&(a=a.toLowerCase())&&(/^(pepflashplayer\.dll|libpepflashplayer\.so|pepperflashplayer\.plugin)$/.test(a)||"chrome.plugin"===a.slice(-13))}function d(a){a&&(i=!0,a.version&&(l=b(a.version)),!l&&a.description&&(l=b(a.description)),a.filename&&(k=c(a.filename)))}var e,f,h,i=!1,j=!1,k=!1,l="";if(g.plugins&&g.plugins.length)e=g.plugins["Shockwave Flash"],d(e),g.plugins["Shockwave Flash 2.0"]&&(i=!0,l="2.0.0.11");else if(g.mimeTypes&&g.mimeTypes.length)h=g.mimeTypes["application/x-shockwave-flash"],e=h&&h.enabledPlugin,d(e);else if("undefined"!=typeof a){j=!0;try{f=new a("ShockwaveFlash.ShockwaveFlash.7"),i=!0,l=b(f.GetVariable("$version"))}catch(n){try{f=new a("ShockwaveFlash.ShockwaveFlash.6"),i=!0,l="6.0.21"}catch(o){try{f=new a("ShockwaveFlash.ShockwaveFlash"),i=!0,l=b(f.GetVariable("$version"))}catch(p){j=!1}}}}I.disabled=i!==!0,I.outdated=l&&m(l)<m(J),I.version=l||"0.0.0",I.pluginType=k?"pepper":j?"activex":i?"netscape":"unknown"};Eb(j);var Fb=function(){return this instanceof Fb?void("function"==typeof Fb._createClient&&Fb._createClient.apply(this,v(arguments))):new Fb};r(Fb,"version",{value:"2.1.6",writable:!1,configurable:!0,enumerable:!0}),Fb.config=function(){return P.apply(this,v(arguments))},Fb.state=function(){return Q.apply(this,v(arguments))},Fb.isFlashUnusable=function(){return R.apply(this,v(arguments))},Fb.on=function(){return S.apply(this,v(arguments))},Fb.off=function(){return T.apply(this,v(arguments))},Fb.handlers=function(){return U.apply(this,v(arguments))},Fb.emit=function(){return V.apply(this,v(arguments))},Fb.create=function(){return W.apply(this,v(arguments))},Fb.destroy=function(){return X.apply(this,v(arguments))},Fb.setData=function(){return Y.apply(this,v(arguments))},Fb.clearData=function(){return Z.apply(this,v(arguments))},Fb.getData=function(){return $.apply(this,v(arguments))},Fb.focus=Fb.activate=function(){return _.apply(this,v(arguments))},Fb.blur=Fb.deactivate=function(){return ab.apply(this,v(arguments))},Fb.activeElement=function(){return bb.apply(this,v(arguments))};var Gb=0,Hb={},Ib=0,Jb={},Kb={};w(O,{autoActivate:!0});var Lb=function(a){var b=this;b.id=""+Gb++,Hb[b.id]={instance:b,elements:[],handlers:{}},a&&b.clip(a),Fb.on("*",function(a){return b.emit(a)}),Fb.on("destroy",function(){b.destroy()}),Fb.create()},Mb=function(a,b){var c,d,e,f={},g=Hb[this.id]&&Hb[this.id].handlers;if("string"==typeof a&&a)e=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.on(c,a[c]);if(e&&e.length){for(c=0,d=e.length;d>c;c++)a=e[c].replace(/^on/,""),f[a]=!0,g[a]||(g[a]=[]),g[a].push(b);if(f.ready&&I.ready&&this.emit({type:"ready",client:this}),f.error){var h=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=h.length;d>c;c++)if(I[h[c]]){this.emit({type:"error",name:"flash-"+h[c],client:this});break}}}return this},Nb=function(a,b){var c,d,e,f,g,h=Hb[this.id]&&Hb[this.id].handlers;if(0===arguments.length)f=q(h);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=h[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return this},Ob=function(a){var b=null,c=Hb[this.id]&&Hb[this.id].handlers;return c&&(b="string"==typeof a&&a?c[a]?c[a].slice(0):[]:x(c)),b},Pb=function(a){if(Ub.call(this,a)){"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(a=w({},a));var b=w({},db(a),{client:this});Vb.call(this,b)}return this},Qb=function(a){a=Wb(a);for(var b=0;b<a.length;b++)if(s.call(a,b)&&a[b]&&1===a[b].nodeType){a[b].zcClippingId?-1===Jb[a[b].zcClippingId].indexOf(this.id)&&Jb[a[b].zcClippingId].push(this.id):(a[b].zcClippingId="zcClippingId_"+Ib++,Jb[a[b].zcClippingId]=[this.id],O.autoActivate===!0&&Xb(a[b]));var c=Hb[this.id]&&Hb[this.id].elements;-1===c.indexOf(a[b])&&c.push(a[b])}return this},Rb=function(a){var b=Hb[this.id];if(!b)return this;var c,d=b.elements;a="undefined"==typeof a?d.slice(0):Wb(a);for(var e=a.length;e--;)if(s.call(a,e)&&a[e]&&1===a[e].nodeType){for(c=0;-1!==(c=d.indexOf(a[e],c));)d.splice(c,1);var f=Jb[a[e].zcClippingId];if(f){for(c=0;-1!==(c=f.indexOf(this.id,c));)f.splice(c,1);0===f.length&&(O.autoActivate===!0&&Yb(a[e]),delete a[e].zcClippingId)}}return this},Sb=function(){var a=Hb[this.id];return a&&a.elements?a.elements.slice(0):[]},Tb=function(){this.unclip(),this.off(),delete Hb[this.id]},Ub=function(a){if(!a||!a.type)return!1;if(a.client&&a.client!==this)return!1;var b=Hb[this.id]&&Hb[this.id].elements,c=!!b&&b.length>0,d=!a.target||c&&-1!==b.indexOf(a.target),e=a.relatedTarget&&c&&-1!==b.indexOf(a.relatedTarget),f=a.client&&a.client===this;return d||e||f?!0:!1},Vb=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=Hb[this.id]&&Hb[this.id].handlers["*"]||[],d=Hb[this.id]&&Hb[this.id].handlers[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},Wb=function(a){return"string"==typeof a&&(a=[]),"number"!=typeof a.length?[a]:a},Xb=function(a){if(a&&1===a.nodeType){var b=function(a){(a||(a=e.event))&&("js"!==a._source&&(a.stopImmediatePropagation(),a.preventDefault()),delete a._source)},c=function(c){(c||(c=e.event))&&(b(c),Fb.focus(a))};a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",b,!1),a.addEventListener("mouseenter",b,!1),a.addEventListener("mouseleave",b,!1),a.addEventListener("mousemove",b,!1),Kb[a.zcClippingId]={mouseover:c,mouseout:b,mouseenter:b,mouseleave:b,mousemove:b}}},Yb=function(a){if(a&&1===a.nodeType){var b=Kb[a.zcClippingId];if("object"==typeof b&&b){for(var c,d,e=["move","leave","enter","out","over"],f=0,g=e.length;g>f;f++)c="mouse"+e[f],d=b[c],"function"==typeof d&&a.removeEventListener(c,d,!1);delete Kb[a.zcClippingId]}}};Fb._createClient=function(){Lb.apply(this,v(arguments))},Fb.prototype.on=function(){return Mb.apply(this,v(arguments))},Fb.prototype.off=function(){return Nb.apply(this,v(arguments))},Fb.prototype.handlers=function(){return Ob.apply(this,v(arguments))},Fb.prototype.emit=function(){return Pb.apply(this,v(arguments))},Fb.prototype.clip=function(){return Qb.apply(this,v(arguments))},Fb.prototype.unclip=function(){return Rb.apply(this,v(arguments))},Fb.prototype.elements=function(){return Sb.apply(this,v(arguments))},Fb.prototype.destroy=function(){return Tb.apply(this,v(arguments))},Fb.prototype.setText=function(a){return Fb.setData("text/plain",a),this},Fb.prototype.setHtml=function(a){return Fb.setData("text/html",a),this},Fb.prototype.setRichText=function(a){return Fb.setData("application/rtf",a),this},Fb.prototype.setData=function(){return Fb.setData.apply(this,v(arguments)),this},Fb.prototype.clearData=function(){return Fb.clearData.apply(this,v(arguments)),this},Fb.prototype.getData=function(){return Fb.getData.apply(this,v(arguments))},"function"==typeof define&&define.amd?define(function(){return Fb}):"object"==typeof module&&module&&"object"==typeof module.exports&&module.exports?module.exports=Fb:a.ZeroClipboard=Fb}(function(){return this||window}());
/**
 * @fileoverview
 * - Using the 'QRCode for Javascript library'
 * - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
 * - this library has no dependencies.
 * 
 * @author davidshimjs
 * @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
 * @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
 */

(function () {
	//---------------------------------------------------------------------
	// QRCode for JavaScript
	//
	// Copyright (c) 2009 Kazuhiko Arase
	//
	// URL: http://www.d-project.com/
	//
	// Licensed under the MIT license:
	//   http://www.opensource.org/licenses/mit-license.php
	//
	// The word "QR Code" is registered trademark of 
	// DENSO WAVE INCORPORATED
	//   http://www.denso-wave.com/qrcode/faqpatent-e.html
	//
	//---------------------------------------------------------------------
	function QR8bitByte(data) {
		this.mode = QRMode.MODE_8BIT_BYTE;
		this.data = data;
		this.parsedData = [];

		// Added to support UTF-8 Characters
		for (var i = 0, l = this.data.length; i < l; i++) {
			var byteArray = [];
			var code = this.data.charCodeAt(i);

			if (code > 0x10000) {
				byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
				byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
				byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
				byteArray[3] = 0x80 | (code & 0x3F);
			} else if (code > 0x800) {
				byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
				byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
				byteArray[2] = 0x80 | (code & 0x3F);
			} else if (code > 0x80) {
				byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
				byteArray[1] = 0x80 | (code & 0x3F);
			} else {
				byteArray[0] = code;
			}

			this.parsedData.push(byteArray);
		}

		this.parsedData = Array.prototype.concat.apply([], this.parsedData);

		if (this.parsedData.length != this.data.length) {
			this.parsedData.unshift(191);
			this.parsedData.unshift(187);
			this.parsedData.unshift(239);
		}
	}

	QR8bitByte.prototype = {
		getLength: function (buffer) {
			return this.parsedData.length;
		},
		write: function (buffer) {
			for (var i = 0, l = this.parsedData.length; i < l; i++) {
				buffer.put(this.parsedData[i], 8);
			}
		}
	};

	function QRCodeModel(typeNumber, errorCorrectLevel) {
		this.typeNumber = typeNumber;
		this.errorCorrectLevel = errorCorrectLevel;
		this.modules = null;
		this.moduleCount = 0;
		this.dataCache = null;
		this.dataList = [];
	}

	QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
	return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
	this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
	if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
	this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
	return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
	return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
	this.modules[r][6]=(r%2==0);}
	for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
	this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
	for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
	for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
	for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
	this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
	var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
	this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
	row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
	var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
	if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
	+buffer.getLengthInBits()
	+">"
	+totalDataCount*8
	+")");}
	if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
	while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
	while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
	buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
	buffer.put(QRCodeModel.PAD1,8);}
	return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
	offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
	var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
	var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
	for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
	return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
	return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
	return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
	return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
	return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
	for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
	if(r==0&&c==0){continue;}
	if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
	if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
	for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
	for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
	for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
	var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
	var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
	return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
	while(n>=256){n-=255;}
	return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
	for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
	for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
	function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
	var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
	this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
	QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
	return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
	var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
	for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
	return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
	QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
	var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
	return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
	QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
	if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
	this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
	
	function _isSupportCanvas() {
		return typeof CanvasRenderingContext2D != "undefined";
	}
	
	// android 2.x doesn't support Data-URI spec
	function _getAndroid() {
		var android = false;
		var sAgent = navigator.userAgent;
		
		if (/android/i.test(sAgent)) { // android
			android = true;
			aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
			
			if (aMat && aMat[1]) {
				android = parseFloat(aMat[1]);
			}
		}
		
		return android;
	}
	
	var svgDrawer = (function() {

		var Drawing = function (el, htOption) {
			this._el = el;
			this._htOption = htOption;
		};

		Drawing.prototype.draw = function (oQRCode) {
			var _htOption = this._htOption;
			var _el = this._el;
			var nCount = oQRCode.getModuleCount();
			var nWidth = Math.floor(_htOption.width / nCount);
			var nHeight = Math.floor(_htOption.height / nCount);

			this.clear();

			function makeSVG(tag, attrs) {
				var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
				for (var k in attrs)
					if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
				return el;
			}

			var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
			svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
			_el.appendChild(svg);

			svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));

			for (var row = 0; row < nCount; row++) {
				for (var col = 0; col < nCount; col++) {
					if (oQRCode.isDark(row, col)) {
						var child = makeSVG("use", {"x": String(row), "y": String(col)});
						child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
						svg.appendChild(child);
					}
				}
			}
		};
		Drawing.prototype.clear = function () {
			while (this._el.hasChildNodes())
				this._el.removeChild(this._el.lastChild);
		};
		return Drawing;
	})();

	var useSVG = document.documentElement.tagName.toLowerCase() === "svg";

	// Drawing in DOM by using Table tag
	var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
		var Drawing = function (el, htOption) {
			this._el = el;
			this._htOption = htOption;
		};
			
		/**
		 * Draw the QRCode
		 * 
		 * @param {QRCode} oQRCode
		 */
		Drawing.prototype.draw = function (oQRCode) {
            var _htOption = this._htOption;
            var _el = this._el;
			var nCount = oQRCode.getModuleCount();
			var nWidth = Math.floor(_htOption.width / nCount);
			var nHeight = Math.floor(_htOption.height / nCount);
			var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
			
			for (var row = 0; row < nCount; row++) {
				aHTML.push('<tr>');
				
				for (var col = 0; col < nCount; col++) {
					aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
				}
				
				aHTML.push('</tr>');
			}
			
			aHTML.push('</table>');
			_el.innerHTML = aHTML.join('');
			
			// Fix the margin values as real size.
			var elTable = _el.childNodes[0];
			var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
			var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
			
			if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
				elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";	
			}
		};
		
		/**
		 * Clear the QRCode
		 */
		Drawing.prototype.clear = function () {
			this._el.innerHTML = '';
		};
		
		return Drawing;
	})() : (function () { // Drawing in Canvas
		function _onMakeImage() {
			this._elImage.src = this._elCanvas.toDataURL("image/png");
			this._elImage.style.display = "block";
			this._elCanvas.style.display = "none";			
		}
		
		// Android 2.1 bug workaround
		// http://code.google.com/p/android/issues/detail?id=5141
		if (this._android && this._android <= 2.1) {
	    	var factor = 1 / window.devicePixelRatio;
	        var drawImage = CanvasRenderingContext2D.prototype.drawImage; 
	    	CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
	    		if (("nodeName" in image) && /img/i.test(image.nodeName)) {
		        	for (var i = arguments.length - 1; i >= 1; i--) {
		            	arguments[i] = arguments[i] * factor;
		        	}
	    		} else if (typeof dw == "undefined") {
	    			arguments[1] *= factor;
	    			arguments[2] *= factor;
	    			arguments[3] *= factor;
	    			arguments[4] *= factor;
	    		}
	    		
	        	drawImage.apply(this, arguments); 
	    	};
		}
		
		/**
		 * Check whether the user's browser supports Data URI or not
		 * 
		 * @private
		 * @param {Function} fSuccess Occurs if it supports Data URI
		 * @param {Function} fFail Occurs if it doesn't support Data URI
		 */
		function _safeSetDataURI(fSuccess, fFail) {
            var self = this;
            self._fFail = fFail;
            self._fSuccess = fSuccess;

            // Check it just once
            if (self._bSupportDataURI === null) {
                var el = document.createElement("img");
                var fOnError = function() {
                    self._bSupportDataURI = false;

                    if (self._fFail) {
                        _fFail.call(self);
                    }
                };
                var fOnSuccess = function() {
                    self._bSupportDataURI = true;

                    if (self._fSuccess) {
                        self._fSuccess.call(self);
                    }
                };

                el.onabort = fOnError;
                el.onerror = fOnError;
                el.onload = fOnSuccess;
                el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
                return;
            } else if (self._bSupportDataURI === true && self._fSuccess) {
                self._fSuccess.call(self);
            } else if (self._bSupportDataURI === false && self._fFail) {
                self._fFail.call(self);
            }
		};
		
		/**
		 * Drawing QRCode by using canvas
		 * 
		 * @constructor
		 * @param {HTMLElement} el
		 * @param {Object} htOption QRCode Options 
		 */
		var Drawing = function (el, htOption) {
    		this._bIsPainted = false;
    		this._android = _getAndroid();
		
			this._htOption = htOption;
			this._elCanvas = document.createElement("canvas");
			this._elCanvas.width = htOption.width;
			this._elCanvas.height = htOption.height;
			el.appendChild(this._elCanvas);
			this._el = el;
			this._oContext = this._elCanvas.getContext("2d");
			this._bIsPainted = false;
			this._elImage = document.createElement("img");
			this._elImage.alt = "Scan me!";
			this._elImage.style.display = "none";
			this._el.appendChild(this._elImage);
			this._bSupportDataURI = null;
		};
			
		/**
		 * Draw the QRCode
		 * 
		 * @param {QRCode} oQRCode 
		 */
		Drawing.prototype.draw = function (oQRCode) {
            var _elImage = this._elImage;
            var _oContext = this._oContext;
            var _htOption = this._htOption;
            
			var nCount = oQRCode.getModuleCount();
			var nWidth = _htOption.width / nCount;
			var nHeight = _htOption.height / nCount;
			var nRoundedWidth = Math.round(nWidth);
			var nRoundedHeight = Math.round(nHeight);

			_elImage.style.display = "none";
			this.clear();
			
			for (var row = 0; row < nCount; row++) {
				for (var col = 0; col < nCount; col++) {
					var bIsDark = oQRCode.isDark(row, col);
					var nLeft = col * nWidth;
					var nTop = row * nHeight;
					_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
					_oContext.lineWidth = 1;
					_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;					
					_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
					
					// 안티 앨리어싱 방지 처리
					_oContext.strokeRect(
						Math.floor(nLeft) + 0.5,
						Math.floor(nTop) + 0.5,
						nRoundedWidth,
						nRoundedHeight
					);
					
					_oContext.strokeRect(
						Math.ceil(nLeft) - 0.5,
						Math.ceil(nTop) - 0.5,
						nRoundedWidth,
						nRoundedHeight
					);
				}
			}
			
			this._bIsPainted = true;
		};
			
		/**
		 * Make the image from Canvas if the browser supports Data URI.
		 */
		Drawing.prototype.makeImage = function () {
			if (this._bIsPainted) {
				_safeSetDataURI.call(this, _onMakeImage);
			}
		};
			
		/**
		 * Return whether the QRCode is painted or not
		 * 
		 * @return {Boolean}
		 */
		Drawing.prototype.isPainted = function () {
			return this._bIsPainted;
		};
		
		/**
		 * Clear the QRCode
		 */
		Drawing.prototype.clear = function () {
			this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
			this._bIsPainted = false;
		};
		
		/**
		 * @private
		 * @param {Number} nNumber
		 */
		Drawing.prototype.round = function (nNumber) {
			if (!nNumber) {
				return nNumber;
			}
			
			return Math.floor(nNumber * 1000) / 1000;
		};
		
		return Drawing;
	})();
	
	/**
	 * Get the type by string length
	 * 
	 * @private
	 * @param {String} sText
	 * @param {Number} nCorrectLevel
	 * @return {Number} type
	 */
	function _getTypeNumber(sText, nCorrectLevel) {			
		var nType = 1;
		var length = _getUTF8Length(sText);
		
		for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
			var nLimit = 0;
			
			switch (nCorrectLevel) {
				case QRErrorCorrectLevel.L :
					nLimit = QRCodeLimitLength[i][0];
					break;
				case QRErrorCorrectLevel.M :
					nLimit = QRCodeLimitLength[i][1];
					break;
				case QRErrorCorrectLevel.Q :
					nLimit = QRCodeLimitLength[i][2];
					break;
				case QRErrorCorrectLevel.H :
					nLimit = QRCodeLimitLength[i][3];
					break;
			}
			
			if (length <= nLimit) {
				break;
			} else {
				nType++;
			}
		}
		
		if (nType > QRCodeLimitLength.length) {
			throw new Error("Too long data");
		}
		
		return nType;
	}

	function _getUTF8Length(sText) {
		var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
		return replacedText.length + (replacedText.length != sText ? 3 : 0);
	}
	
	/**
	 * @class QRCode
	 * @constructor
	 * @example 
	 * new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
	 *
	 * @example
	 * var oQRCode = new QRCode("test", {
	 *    text : "http://naver.com",
	 *    width : 128,
	 *    height : 128
	 * });
	 * 
	 * oQRCode.clear(); // Clear the QRCode.
	 * oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
	 *
	 * @param {HTMLElement|String} el target element or 'id' attribute of element.
	 * @param {Object|String} vOption
	 * @param {String} vOption.text QRCode link data
	 * @param {Number} [vOption.width=256]
	 * @param {Number} [vOption.height=256]
	 * @param {String} [vOption.colorDark="#000000"]
	 * @param {String} [vOption.colorLight="#ffffff"]
	 * @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H] 
	 */
	window.QRCode = function (el, vOption) {
		this._htOption = {
			width : 256, 
			height : 256,
			typeNumber : 4,
			colorDark : "#000000",
			colorLight : "#ffffff",
			correctLevel : QRErrorCorrectLevel.H
		};
		
		if (typeof vOption === 'string') {
			vOption	= {
				text : vOption
			};
		}
		
		// Overwrites options
		if (vOption) {
			for (var i in vOption) {
				this._htOption[i] = vOption[i];
			}
		}
		
		if (typeof el == "string") {
			el = document.getElementById(el);
		}
		
		this._android = _getAndroid();
		this._el = el;
		this._oQRCode = null;
		this._oDrawing = new Drawing(this._el, this._htOption);
		
		if (this._htOption.text) {
			this.makeCode(this._htOption.text);	
		}
	};
	
	/**
	 * Make the QRCode
	 * 
	 * @param {String} sText link data
	 */
	QRCode.prototype.makeCode = function (sText) {
		this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
		this._oQRCode.addData(sText);
		this._oQRCode.make();
		this._el.title = sText;
		this._oDrawing.draw(this._oQRCode);			
		this.makeImage();
	};
	
	/**
	 * Make the Image from Canvas element
	 * - It occurs automatically
	 * - Android below 3 doesn't support Data-URI spec.
	 * 
	 * @private
	 */
	QRCode.prototype.makeImage = function () {
		if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
			this._oDrawing.makeImage();
		}
	};
	
	/**
	 * Clear the QRCode
	 */
	QRCode.prototype.clear = function () {
		this._oDrawing.clear();
	};
	
	/**
	 * @name QRCode.CorrectLevel
	 */
	QRCode.CorrectLevel = QRErrorCorrectLevel;
})();

var jhtmls="undefined"==typeof exports?jhtmls||{}:exports;void function(e){"use strict";function n(e){return String(e).replace(/["<>& ]/g,function(e){return"&"+u[e]+";"})}function t(e){var n=[];return n.push("with(this){"),n.push(e.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/g,function(e){return['!#{unescape("',escape(e),'")}'].join("")}).replace(/[\r\n]+/g,"\n").replace(/^\n+|\s+$/gm,"").replace(/^([ \w\t_$]*([^&\^?|\n\w\/'"{}\[\]+\-():; \t=\.$_]|:\/\/).*$|^(?!\s*(else|do|try|finally|void|typeof\s[\w$_]*)\s*$)[^'":;{}()\n|=&\/^?]+$)\s?/gm,function(e){return e=e.replace(/&none;/g,"").replace(/["'\\]/g,"\\$&").replace(/\n/g,"\\n").replace(/(!?#)\{(.*?)\}|(!?\$)([a-z_]+\w*(?:\.[a-z_]+\w*)*)/g,function(e,n,t,r,u){if(r&&(n=r,t=u),!t)return"";t=t.replace(/\\n/g,"\n").replace(/\\([\\'"])/g,"$1");var o=/^[a-z$][\w+$]+$/i.test(t)&&!/^(true|false|NaN|null|this)$/.test(t);return["',",o?["typeof ",t,"==='undefined'?'':"].join(""):"","#"===n||"$"===n?"_encode_":"","(",t,"),'"].join("")}),e=["'",e,"'"].join("").replace(/^'',|,''$/g,""),e?["_output_.push(",e,");"].join(""):""})),n.push("}"),new Function("_output_","_encode_","helper","jhtmls",n.join(""))}function r(r,u,o){"function"==typeof r&&(r=String(r).replace(/^[^\{]*\{\s*\/\*!?[ \f\t\v]*\n?|[ \f\t\v]*\*\/[;|\s]*\}$/g,""));var i=t(r),s=function(t,r){r=r||e;var u=[];return i.call(t,u,n,r,e),u.join("")};return arguments.length<=1?s:s(u,o)}var u={'"':"quot","<":"lt",">":"gt","&":"amp"," ":"nbsp"};e.render=r}(jhtmls);
/*!
 * ====================================================
 * Flex UI - v1.0.0 - 2015-01-12
 * https://github.com/fex-team/fui
 * GitHub: https://github.com/fex-team/fui.git 
 * Copyright (c) 2015 Baidu Kity Group; Licensed MIT
 * ====================================================
 */

(function () {
var _p = {
    r: function(index) {
        if (_p[index].inited) {
            return _p[index].value;
        }
        if (typeof _p[index].value === "function") {
            var module = {
                exports: {}
            }, returnValue = _p[index].value(null, module.exports, module);
            _p[index].inited = true;
            _p[index].value = returnValue;
            if (returnValue !== undefined) {
                return returnValue;
            } else {
                for (var key in module.exports) {
                    if (module.exports.hasOwnProperty(key)) {
                        _p[index].inited = true;
                        _p[index].value = module.exports;
                        return module.exports;
                    }
                }
            }
        } else {
            _p[index].inited = true;
            return _p[index].value;
        }
    }
};

//src/base/creator.js
/**
 * UI构造工厂, 提供可通过参数配置项创建多个构件的机制.
 */
_p[0] = {
    value: function(require) {
        var Creator = {}, $ = _p.r(4), FUI_NS = _p.r(11);
        $.extend(Creator, {
            parse: function(options) {
                var pool = [];
                if ($.isArray(options)) {
                    $.each(options, function(i, opt) {
                        pool.push(getInstance(opt));
                    });
                    return pool;
                } else {
                    return getInstance(options);
                }
            }
        });
        function getInstance(option) {
            var Constructor = FUI_NS[option.clazz];
            if (!Constructor) {
                return null;
            }
            return new Constructor(option);
        }
        return Creator;
    }
};

//src/base/exports.js
/**
 * 模块暴露
 */
_p[1] = {
    value: function(require) {
        var FUI_NS = _p.r(11);
        // 配置参数必须最先注册
        FUI_NS.___register({
            ALLOW_FOCUS: true
        });
        FUI_NS.___register({
            Widget: _p.r(60),
            Icon: _p.r(42),
            Label: _p.r(48),
            Button: _p.r(37),
            ToggleButton: _p.r(59),
            Buttonset: _p.r(36),
            Separator: _p.r(56),
            Item: _p.r(46),
            Input: _p.r(45),
            InputButton: _p.r(43),
            Mask: _p.r(49),
            ColorPicker: _p.r(38),
            Tabs: _p.r(58),
            SpinButton: _p.r(57),
            Container: _p.r(39),
            Panel: _p.r(51),
            PPanel: _p.r(54),
            LabelPanel: _p.r(47),
            Menu: _p.r(50),
            InputMenu: _p.r(44),
            ButtonMenu: _p.r(34),
            DropPanel: _p.r(41),
            Popup: _p.r(53),
            PopupMenu: _p.r(52),
            SelectMenu: _p.r(55),
            Dialog: _p.r(40),
            Utils: _p.r(13),
            Creator: _p.r(0)
        });
        FUI_NS.__export();
    }
};

//src/base/extensions.js
/**
 * 扩展模块暴露
 */
_p[2] = {
    value: function(require) {
        var FUI_NS = _p.r(11);
        FUI_NS.___register({
            TablePicker: _p.r(17)
        });
    }
};

//src/base/jhtmls.js
/**
 * jhtmls模板引擎
 */
_p[3] = {
    value: function() {
        /* global jhtmls: true */
        return jhtmls;
    }
};

//src/base/jquery.js
/**
 * jquery模块封装
 */
_p[4] = {
    value: function(require) {
        return window.jQuery;
    }
};

//src/base/kit/class.js
/**
 * @description 创建一个类
 * @param {String}    fullClassName  类全名，包括命名空间。
 * @param {Plain}     defines        要创建的类的特性
 *     defines.constructor  {Function}       类的构造函数，实例化的时候会被调用。
 *     defines.base         {String}         基类的名称。名称要使用全名。（因为base是javascript未来保留字，所以不用base）
 *     defines.mixin        {Array<String>}  要混合到新类的类集合
 *     defines.<method>     {Function}       其他类方法
 *
 * TODO:
 *     Mixin 构造函数调用支持
 */
_p[5] = {
    value: function(require, exports) {
        // just to bind context
        Function.prototype.bind = Function.prototype.bind || function(thisObj) {
            var args = Array.prototype.slice.call(arguments, 1);
            return this.apply(thisObj, args);
        };
        // 所有类的基类
        function Class() {}
        Class.__KityClassName = "Class";
        // 提供 base 调用支持
        Class.prototype.base = function(name) {
            var caller = arguments.callee.caller;
            var method = caller.__KityMethodClass.__KityBaseClass.prototype[name];
            return method.apply(this, Array.prototype.slice.call(arguments, 1));
        };
        // 直接调用 base 类的同名方法
        Class.prototype.callBase = function() {
            var caller = arguments.callee.caller;
            var method = caller.__KityMethodClass.__KityBaseClass.prototype[caller.__KityMethodName];
            return method.apply(this, arguments);
        };
        Class.prototype.mixin = function(name) {
            var caller = arguments.callee.caller;
            var mixins = caller.__KityMethodClass.__KityMixins;
            if (!mixins) {
                return this;
            }
            var method = mixins[name];
            return method.apply(this, Array.prototype.slice.call(arguments, 1));
        };
        Class.prototype.callMixin = function() {
            var caller = arguments.callee.caller;
            var methodName = caller.__KityMethodName;
            var mixins = caller.__KityMethodClass.__KityMixins;
            if (!mixins) {
                return this;
            }
            var method = mixins[methodName];
            if (methodName == "constructor") {
                for (var i = 0, l = method.length; i < l; i++) {
                    method[i].call(this);
                }
                return this;
            } else {
                return method.apply(this, arguments);
            }
        };
        Class.prototype.pipe = function(fn) {
            if (typeof fn == "function") {
                fn.call(this, this);
            }
            return this;
        };
        Class.prototype.getType = function() {
            return this.__KityClassName;
        };
        Class.prototype.getClass = function() {
            return this.constructor;
        };
        // 检查基类是否调用了父类的构造函数
        // 该检查是弱检查，假如调用的代码被注释了，同样能检查成功（这个特性可用于知道建议调用，但是出于某些原因不想调用的情况）
        function checkBaseConstructorCall(targetClass, classname) {
            var code = targetClass.toString();
            if (!/this\.callBase/.test(code)) {
                throw new Error(classname + " : 类构造函数没有调用父类的构造函数！为了安全，请调用父类的构造函数");
            }
        }
        var KITY_INHERIT_FLAG = "__KITY_INHERIT_FLAG_" + +new Date();
        function inherit(constructor, BaseClass, classname) {
            var KityClass = eval("(function " + classname + "( __inherit__flag ) {" + "if( __inherit__flag != KITY_INHERIT_FLAG ) {" + "KityClass.__KityConstructor.apply(this, arguments);" + "}" + "this.__KityClassName = KityClass.__KityClassName;" + "})||0");
            KityClass.__KityConstructor = constructor;
            KityClass.prototype = new BaseClass(KITY_INHERIT_FLAG);
            for (var methodName in BaseClass.prototype) {
                if (BaseClass.prototype.hasOwnProperty(methodName) && methodName.indexOf("__Kity") !== 0) {
                    KityClass.prototype[methodName] = BaseClass.prototype[methodName];
                }
            }
            KityClass.prototype.constructor = KityClass;
            return KityClass;
        }
        function mixin(NewClass, mixins) {
            if (false === mixins instanceof Array) {
                return NewClass;
            }
            var i, length = mixins.length, proto, method;
            NewClass.__KityMixins = {
                constructor: []
            };
            for (i = 0; i < length; i++) {
                proto = mixins[i].prototype;
                for (method in proto) {
                    if (false === proto.hasOwnProperty(method) || method.indexOf("__Kity") === 0) {
                        continue;
                    }
                    if (method === "constructor") {
                        // constructor 特殊处理
                        NewClass.__KityMixins.constructor.push(proto[method]);
                    } else {
                        NewClass.prototype[method] = NewClass.__KityMixins[method] = proto[method];
                    }
                }
            }
            return NewClass;
        }
        function extend(BaseClass, extension) {
            if (extension.__KityClassName) {
                extension = extension.prototype;
            }
            for (var methodName in extension) {
                if (extension.hasOwnProperty(methodName) && methodName.indexOf("__Kity") && methodName != "constructor") {
                    var method = BaseClass.prototype[methodName] = extension[methodName];
                    method.__KityMethodClass = BaseClass;
                    method.__KityMethodName = methodName;
                }
            }
            return BaseClass;
        }
        Class.prototype._accessProperty = function() {
            return this._propertyRawData || (this._propertyRawData = {});
        };
        exports.createClass = function(classname, defines) {
            var constructor, NewClass, BaseClass;
            if (arguments.length === 1) {
                defines = arguments[0];
                classname = "AnonymousClass";
            }
            BaseClass = defines.base || Class;
            if (defines.hasOwnProperty("constructor")) {
                constructor = defines.constructor;
                if (BaseClass != Class) {
                    checkBaseConstructorCall(constructor, classname);
                }
            } else {
                constructor = function() {
                    this.callBase.apply(this, arguments);
                    this.callMixin.apply(this, arguments);
                };
            }
            NewClass = inherit(constructor, BaseClass, classname);
            NewClass = mixin(NewClass, defines.mixins);
            NewClass.__KityClassName = constructor.__KityClassName = classname;
            NewClass.__KityBaseClass = constructor.__KityBaseClass = BaseClass;
            NewClass.__KityMethodName = constructor.__KityMethodName = "constructor";
            NewClass.__KityMethodClass = constructor.__KityMethodClass = NewClass;
            // 下面这些不需要拷贝到原型链上
            delete defines.mixins;
            delete defines.constructor;
            delete defines.base;
            NewClass = extend(NewClass, defines);
            return NewClass;
        };
        exports.extendClass = extend;
    }
};

//src/base/kit/common.js
/**
 * 通用工具包
 */
_p[6] = {
    value: function(require) {
        var $ = _p.r(4), __marker = "__fui__marker__" + +new Date();
        return {
            isElement: function(target) {
                return target.nodeType === 1;
            },
            getMarker: function() {
                return __marker;
            },
            getRect: function(node) {
                var rect = node.getBoundingClientRect();
                return {
                    width: rect.width,
                    height: rect.height,
                    top: rect.top,
                    bottom: rect.bottom,
                    left: rect.left,
                    right: rect.right
                };
            },
            getBound: function(node) {
                var w = 0, h = 0;
                if (node.tagName.toLowerCase() === "body") {
                    h = $(this.getView(node));
                    w = h.width();
                    h = h.height();
                    return {
                        top: 0,
                        left: 0,
                        bottom: h,
                        right: w,
                        width: w,
                        height: h
                    };
                } else {
                    return this.getRect(node);
                }
            },
            getView: function(node) {
                return node.ownerDocument.defaultView || node.ownerDocument.parentWindow;
            }
        };
    }
};

//src/base/kit/compile.js
/**
 * 模板编译器
 */
_p[7] = {
    value: function(require) {
        var jhtmls = _p.r(3), $ = _p.r(4);
        var Helper = {
            forEach: function(arras, cb) {
                $.each(arras, function(i, item) {
                    cb.call(null, i, item);
                });
            }
        };
        return {
            compile: function(tpl, data) {
                tpl = $.trim(tpl);
                if (tpl.length === 0) {
                    return "";
                }
                return jhtmls.render(tpl, data, Helper);
            }
        };
    }
};

//src/base/kit/draggable.js
/**
 * Draggable Lib
 */
_p[8] = {
    value: function(require, exports) {
        var $ = _p.r(4), common = _p.r(6), DEFAULT_OPTIONS = {
            handler: null,
            target: null,
            axis: "all",
            range: null
        };
        function Draggable(options) {
            this.__options = $.extend({}, DEFAULT_OPTIONS, options);
            this.__started = false;
            this.__point = {
                x: 0,
                y: 0
            };
            this.__location = {
                x: 0,
                y: 0
            };
            this.__range = {
                top: 0,
                left: 0,
                bottom: 0,
                right: 0
            };
        }
        $.extend(Draggable.prototype, {
            bind: function(target) {
                if (target) {
                    this.__options.target = target;
                }
                if (!this.__options.target) {
                    throw new Error("target unset");
                }
                this.__target = this.__options.target;
                this.__handler = this.__options.handler;
                this.__rangeNode = this.__options.range;
                this.__initOptions();
                this.__initEnv();
                this.__initEvent();
            },
            __initEvent: function() {
                var handler = this.__handler, _self = this;
                $(handler).on("mousedown", function(e) {
                    if (e.which !== 1) {
                        return;
                    }
                    var location = common.getRect(handler);
                    e.preventDefault();
                    _self.__started = true;
                    _self.__point = {
                        x: e.clientX,
                        y: e.clientY
                    };
                    _self.__location = {
                        x: location.left,
                        y: location.top
                    };
                    _self.__range = _self.__getRange();
                });
                $(handler.ownerDocument).on("mousemove", function(e) {
                    if (!_self.__started) {
                        return;
                    }
                    var x = e.clientX, y = e.clientY;
                    if (_self.__allowAxisX) {
                        _self.__xMove(x);
                    }
                    if (_self.__allowAxisY) {
                        _self.__yMove(y);
                    }
                }).on("mouseup", function(e) {
                    _self.__started = false;
                });
            },
            __xMove: function(x) {
                var diff = x - this.__point.x;
                diff = this.__location.x + diff;
                if (diff < this.__range.left) {
                    diff = this.__range.left;
                } else if (diff > this.__range.right) {
                    diff = this.__range.right;
                }
                this.__target.style.left = diff + "px";
            },
            __yMove: function(y) {
                var diff = y - this.__point.y;
                diff = this.__location.y + diff;
                if (diff < this.__range.top) {
                    diff = this.__range.top;
                } else if (diff > this.__range.bottom) {
                    diff = this.__range.bottom;
                }
                this.__target.style.top = diff + "px";
            },
            __initEnv: function() {
                var $handler = $(this.__handler);
                $handler.css("cursor", "move");
            },
            __initOptions: function() {
                if (!this.__handler) {
                    this.__handler = this.__target;
                }
                if (!this.__rangeNode) {
                    this.__rangeNode = this.__options.target.ownerDocument.body;
                }
                this.__allowAxisX = this.__options.axis !== "y";
                this.__allowAxisY = this.__options.axis !== "x";
            },
            __getRange: function() {
                var range = this.__rangeNode, targetRect = common.getRect(this.__target);
                if (range.tagName.toLowerCase() === "body") {
                    range = $(this.__rangeNode.ownerDocument);
                    range = {
                        top: 0,
                        left: 0,
                        bottom: range.height(),
                        right: range.width()
                    };
                } else {
                    range = common.getRect(range);
                }
                return {
                    top: range.top,
                    left: range.left,
                    bottom: range.bottom - targetRect.height,
                    right: range.right - targetRect.width
                };
            }
        });
        return function(options) {
            return new Draggable(options);
        };
    }
};

//src/base/kit/extend.js
/**
 * 弥补jQuery的extend在克隆对象和数组时存在的问题
 */
_p[9] = {
    value: function(require) {
        var $ = _p.r(4);
        function extend(target) {
            var isPlainObject = false, isArray = false, sourceObj = null;
            if (arguments.length === 1) {
                return copy(target);
            }
            $.each([].slice.call(arguments, 1), function(i, source) {
                for (var key in source) {
                    sourceObj = source[key];
                    if (!source.hasOwnProperty(key)) {
                        continue;
                    }
                    isPlainObject = $.isPlainObject(sourceObj);
                    isArray = $.isArray(sourceObj);
                    if (!isPlainObject && !isArray) {
                        target[key] = source[key];
                    } else if (isPlainObject) {
                        if (!$.isPlainObject(target[key])) {
                            target[key] = {};
                        }
                        target[key] = extend(target[key], sourceObj);
                    } else if (isArray) {
                        target[key] = extend(sourceObj);
                    }
                }
            });
            return target;
        }
        function copy(target) {
            var tmp = null;
            if ($.isPlainObject(target)) {
                return extend({}, target);
            } else if ($.isArray(target)) {
                tmp = [];
                $.each(target, function(index, item) {
                    if ($.isPlainObject(item) || $.isArray(item)) {
                        tmp.push(copy(item));
                    } else {
                        tmp.push(item);
                    }
                });
                return tmp;
            } else {
                return target;
            }
        }
        return extend;
    }
};

//src/base/kit/widget.js
/**
 * 构件相关工具方法
 */
_p[10] = {
    value: function(require) {
        return {
            isContainer: function(widget) {
                return widget.__widgetType === "container";
            }
        };
    }
};

//src/base/ns.js
/**
 * FUI名称空间
 */
_p[11] = {
    value: function() {
        // 容纳所有构件的实例池
        var WIDGET_POOL = {};
        return {
            widgets: WIDGET_POOL,
            /**
         * 暴露命名空间本身
         * @private
         */
            __export: function() {
                window.FUI = this;
            },
            ___register: function(widgetName, widget) {
                if (typeof widgetName === "string") {
                    this[widgetName] = widget;
                } else {
                    widget = widgetName;
                    for (var key in widget) {
                        if (widget.hasOwnProperty(key)) {
                            this[key] = widget[key];
                        }
                    }
                }
            },
            __registerInstance: function(widget) {
                WIDGET_POOL[widget.getId()] = widget;
            }
        };
    }
};

//src/base/sysconf.js
/**
 * UI系统配置
 */
_p[12] = {
    value: function(require) {
        var NS = _p.r(11);
        return {
            classPrefix: "fui-",
            layout: {
                TOP: "top",
                LEFT: "left",
                BOTTOM: "bottom",
                RIGHT: "right",
                CENTER: "center",
                MIDDLE: "middle",
                // 内部定位
                LEFT_TOP: "left-top",
                RIGHT_TOP: "right-top",
                LEFT_BOTTOM: "left-bottom",
                RIGHT_BOTTOM: "right-bottom"
            },
            allowFocus: !!NS.ALLOW_FOCUS,
            control: {
                input: 1,
                textarea: 1,
                button: 1,
                select: 1,
                option: 1,
                object: 1,
                embed: 1
            }
        };
    }
};

//src/base/utils.js
/**
 * utils类包， 提供常用操作的封装，补充jQuery的不足
 */
_p[13] = {
    value: function(require) {
        var $ = _p.r(4), Utils = {
            Tpl: _p.r(7),
            Widget: _p.r(10),
            createDraggable: _p.r(8)
        };
        return $.extend(Utils, _p.r(6), _p.r(5));
    }
};

//src/ext/word/tpl/t-picker.js
_p[14] = {
    value: function() {
        return '<div unselectable="on" class="fui-t-picker"></div>\n';
    }
};

//src/ext/word/tpl/table-picker.js
_p[15] = {
    value: function() {
        return '<div unselectable="on" class="fui-table-picker"></div>\n';
    }
};

//src/ext/word/widget/t-picker.js
/**
 * TPicker -- table 选择器
 */
_p[16] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), tpl = _p.r(14);
        return _p.r(13).createClass("TPicker", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    // 10行 10列
                    row: 10,
                    col: 10
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "TPicker";
                this.__tpl = tpl;
                // 背板
                this.__backplane = null;
            },
            __render: function() {
                this.callBase();
                this.__backplane = this.__createBackplane();
                this.__element.appendChild(this.__backplane);
            },
            __initEvent: function() {
                var _self = this;
                var info = {};
                this.callBase();
                $(this.__backplane).delegate("td", "mousemove", function(e) {
                    info = e.target.getAttribute("data-index").split(",");
                    info = {
                        row: parseInt(info[0], 10),
                        col: parseInt(info[1], 10)
                    };
                    _self.__update(info.row, info.col);
                });
                $(this.__backplane).on("click", function(e) {
                    _self.__select(info.row, info.col);
                });
            },
            __select: function(row, col) {
                this.trigger("pickerselect", {
                    row: row,
                    col: col
                });
            },
            __update: function(row, col) {
                var tr = null, rowCount = this.__options.row, colCount = this.__options.col, className = CONF.classPrefix + "table-picker-hoverin";
                for (var i = 0; i < rowCount; i++) {
                    tr = this.__backplane.rows[i];
                    for (var j = 0; j < colCount; j++) {
                        if (i <= row && j <= col) {
                            tr.cells[j].className = className;
                        } else {
                            tr.cells[j].className = "";
                        }
                    }
                }
                this.trigger("pickerhover", {
                    row: row,
                    col: col
                });
            },
            __createBackplane: function() {
                var tpl = [], tmp = null;
                for (var i = 0, len = this.__options.row; i < len; i++) {
                    tmp = [];
                    for (var j = 0, jlen = this.__options.col; j < jlen; j++) {
                        tmp.push('<td data-index="' + i + "," + j + '"></td>');
                    }
                    tpl.push("<tr>" + tmp.join("") + "</tr>");
                }
                tpl = $("<table><tbody>" + tpl.join("") + "</tbody></table>");
                tpl.addClass(CONF.classPrefix + "t-picker-table");
                return tpl[0];
            }
        });
    }
};

//src/ext/word/widget/table-picker.js
/**
 * Table选择器构件
 */
_p[17] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), tpl = _p.r(15), Label = _p.r(48), TPicker = _p.r(16), Button = _p.r(37), PPanel = _p.r(54), Mask = _p.r(49);
        return _p.r(13).createClass("TablePicker", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    button: null,
                    row: 10,
                    col: 10
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            open: function() {
                this.__panelWidget.show();
                this.__maskWidget.show();
            },
            close: function() {
                this.__panelWidget.hide();
                this.__maskWidget.hide();
            },
            // Overload
            appendTo: function(container) {
                container.appendChild(this.__buttonWidget.getElement());
            },
            getButton: function() {
                return this.__buttonWidget;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "TablePicker";
                this.__tpl = tpl;
                this.__pickerWidget = null;
                this.__labelWidget = null;
                this.__buttonWidget = null;
                this.__panelWidget = null;
                this.__maskWidget = null;
            },
            __render: function() {
                this.callBase();
                this.__pickerWidget = new TPicker(this.__options);
                this.__labelWidget = new Label({
                    text: "插入表格"
                });
                this.__buttonWidget = new Button(this.__options.button);
                this.__panelWidget = new PPanel({
                    className: CONF.classPrefix + "table-picker-panel",
                    column: true,
                    resize: "none"
                });
                this.__maskWidget = new Mask();
                this.__panelWidget.appendWidget(this.__labelWidget);
                this.__panelWidget.appendWidget(this.__pickerWidget);
                this.__panelWidget.positionTo(this.__buttonWidget);
            },
            __initEvent: function() {
                var _self = this;
                this.callBase();
                this.__buttonWidget.on("btnclick", function(e) {
                    _self.open();
                });
                this.__maskWidget.on("maskclick", function(e) {
                    _self.close();
                });
                this.__pickerWidget.on("pickerhover", function(e, info) {
                    var row = info.row + 1, col = info.col + 1;
                    _self.__labelWidget.setText(row + "x" + col + " 表格");
                }).on("pickerselect", function(e, info) {
                    var row = info.row + 1, col = info.col + 1;
                    _self.close();
                    _self.trigger("pickerselect", {
                        row: row,
                        col: col
                    });
                });
            },
            __createBackplane: function() {
                var tpl = [], tmp = null;
                for (var i = 0, len = this.__options.row; i < len; i++) {
                    tmp = [];
                    for (var j = 0, jlen = this.__options.col; j < jlen; j++) {
                        tmp.push('<td data-index="' + i + "," + j + '"></td>');
                    }
                    tpl.push("<tr>" + tmp.join("") + "</tr>");
                }
                tpl = $("<table><tbody>" + tpl.join("") + "</tbody></table>");
                tpl.addClass(CONF.classPrefix + "t-picker-table");
                return tpl[0];
            }
        });
    }
};

//src/tpl/button-menu.js
_p[18] = {
    value: function() {
        return '<div unselectable="on" class="fui-button-menu"></div>\n';
    }
};

//src/tpl/button.js
_p[19] = {
    value: function() {
        return '<div unselectable="on" class="fui-button"></div>\n';
    }
};

//src/tpl/colorpicker.js
_p[20] = {
    value: function() {
        return '<div unselectable="on" class="fui-colorpicker-container">\n' + '<div unselectable="on" class="fui-colorpicker-toolbar">\n' + '<div unselectable="on" class="fui-colorpicker-preview"></div>\n' + '<div unselectable="on" class="fui-colorpicker-clear">$clearText</div>\n' + "</div>\n" + '<div unselectable="on" class="fui-colorpicker-title">$commonText</div>\n' + '<div unselectable="on" class="fui-colorpicker-commoncolor">\n' + "helper.forEach( commonColor, function ( index, colors ) {\n" + '<div unselectable="on" class="fui-colorpicker-colors fui-colorpicker-colors-line$index">\n' + "helper.forEach( colors, function( i, color ) {\n" + '<span unselectable="on" class="fui-colorpicker-item" style="background-color: $color; border-color: #{color.toLowerCase() == \'#ffffff\' ? \'#eeeeee\': color};" data-color="$color"></span>\n' + "});\n" + "</div>\n" + "} );\n" + "</div>\n" + '<div unselectable="on" class="fui-colorpicker-title">$standardText</div>\n' + '<div unselectable="on" class="fui-colorpicker-standardcolor fui-colorpicker-colors">\n' + "helper.forEach( standardColor, function ( i, color ) {\n" + '<span unselectable="on" class="fui-colorpicker-item" style="background-color: $color; border-color: $color;" data-color="$color"></span>\n' + "} );\n" + "</div>\n" + "</div>\n";
    }
};

//src/tpl/dialog.js
_p[21] = {
    value: function() {
        return '<div unselectable="on" class="fui-dialog-wrap">\n' + '<div unselectable="on" class="fui-dialog-head">\n' + '<h1 unselectable="on" class="fui-dialog-caption">$caption</h1>\n' + "</div>\n" + '<div unselectable="on" class="fui-dialog-body"></div>\n' + '<div unselectable="on" class="fui-dialog-foot"></div>\n' + "</div>\n";
    }
};

//src/tpl/drop-panel.js
_p[22] = {
    value: function() {
        return "<div unselectable=\"on\" class=\"fui-drop-panel\"  #{ text ? 'title=\"' + m.text + '\"' : '' }></div>\n";
    }
};

//src/tpl/icon.js
_p[23] = {
    value: function() {
        return '<div unselectable="on" class="fui-icon" >\n' + "if ( this.img ) {\n" + '<img unselectable="on" src="#{this.img}" >\n' + "}\n" + "</div>\n";
    }
};

//src/tpl/input-button.js
_p[24] = {
    value: function() {
        return '<div unselectable="on" class="fui-input-button"></div>\n';
    }
};

//src/tpl/input-menu.js
_p[25] = {
    value: function() {
        return '<div unselectable="on" class="fui-input-menu"></div>\n';
    }
};

//src/tpl/input.js
_p[26] = {
    value: function() {
        return '<input unselectable="on" class="fui-input"  autocomplete="off" !#{ value ? \'value="\' + value + \'"\' : \'\'}>\n';
    }
};

//src/tpl/item.js
_p[27] = {
    value: function() {
        return "<div unselectable=\"on\" class=\"fui-item!#{ selected ? ' fui-item-selected': '' }\" ></div>\n";
    }
};

//src/tpl/label.js
_p[28] = {
    value: function() {
        return '<div unselectable="on" class="fui-label">$text</div>\n';
    }
};

//src/tpl/mask.js
_p[29] = {
    value: function() {
        return '<div unselectable="on" class="fui-mask" style="background-color: $bgcolor; opacity: $opacity;"></div>\n';
    }
};

//src/tpl/panel.js
_p[30] = {
    value: function() {
        return '<div unselectable="on" class="fui-panel"></div>\n';
    }
};

//src/tpl/separator.js
_p[31] = {
    value: function() {
        return '<div unselectable="on" class="fui-separator"></div>\n';
    }
};

//src/tpl/spin-button.js
_p[32] = {
    value: function() {
        return '<div unselectable="on" class="fui-spin-button"></div>\n';
    }
};

//src/tpl/tabs.js
_p[33] = {
    value: function() {
        return '<div unselectable="on" class="fui-tabs">\n' + '<div unselectable="on" class="fui-tabs-button-wrap"></div>\n' + '<div unselectable="on" class="fui-tabs-panel-wrap"></div>\n' + "</div>\n";
    }
};

//src/widget/button-menu.js
/**
 * Button对象
 * 通用按钮构件
 */
_p[34] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), tpl = _p.r(18), Button = _p.r(37), Menu = _p.r(50), Mask = _p.r(49), LAYOUT = CONF.layout;
        return _p.r(13).createClass("ButtonMenu", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    // item选项
                    menu: null,
                    mask: null,
                    buttons: [],
                    selected: -1,
                    layout: LAYOUT.RIGHT
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            open: function() {
                this.__openState = true;
                this.__maskWidget.show();
                this.__menuWidget.show();
                this.addClass(CONF.classPrefix + "button-active");
            },
            close: function() {
                this.__openState = false;
                this.__maskWidget.hide();
                this.__menuWidget.hide();
                this.removeClass(CONF.classPrefix + "button-active");
            },
            isOpen: function() {
                return !!this.__openState;
            },
            getSelected: function() {
                return this.__menuWidget.getSelected();
            },
            getSelectedItem: function() {
                return this.__menuWidget.getSelectedItem();
            },
            getValue: function() {
                return this.getSelectedItem().getValue();
            },
            __render: function() {
                this.callBase();
                this.__initButtons();
                this.__menuWidget = new Menu(this.__options.menu);
                this.__maskWidget = new Mask(this.__options.mask);
                this.__menuWidget.positionTo(this.__element);
                this.__menuWidget.appendTo(this.__element.ownerDocument.body);
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "ButtonMenu";
                this.__tpl = tpl;
                this.__buttonWidgets = null;
                this.__menuWidget = null;
                this.__maskWidget = null;
                this.__openState = false;
                if (this.__options.selected !== -1) {
                    this.__options.menu.selected = this.__options.selected;
                }
            },
            __initButtons: function() {
                var buttons = [], ele = this.__element, btn = null, lastIndex = this.__options.buttons.length - 1;
                if (this.__options.layout === LAYOUT.TOP || this.__options.layout === LAYOUT.LEFT) {
                    btn = new Button(this.__options.buttons[lastIndex]);
                    btn.appendTo(ele);
                } else {
                    lastIndex = -1;
                }
                $.each(this.__options.buttons, function(index, options) {
                    if (lastIndex !== index) {
                        var button = new Button(options);
                        button.appendTo(ele);
                        buttons.push(button);
                    } else {
                        buttons.push(btn);
                    }
                });
                this.addClass(CONF.classPrefix + "layout-" + this.__options.layout);
                buttons[buttons.length - 1].addClass(CONF.classPrefix + "open-btn");
                this.__buttonWidgets = buttons;
            },
            __initEvent: function() {
                var lastBtn = this.__buttonWidgets[this.__buttonWidgets.length - 1], _self = this;
                this.callBase();
                lastBtn.on("click", function(e) {
                    _self.open();
                });
                this.__maskWidget.on("maskclick", function() {
                    _self.close();
                });
                this.__menuWidget.on("select", function(e, info) {
                    e.stopPropagation();
                    _self.close();
                    _self.trigger("select", info);
                }).on("change", function(e, info) {
                    _self.trigger("change", info);
                });
                this.on("btnclick", function(e) {
                    e.stopPropagation();
                    var btnIndex = $.inArray(e.widget, this.__buttonWidgets);
                    if (btnIndex > -1 && btnIndex < this.__buttonWidgets.length - 1) {
                        this.trigger("buttonclick", {
                            button: this.__buttonWidgets[btnIndex]
                        });
                    }
                });
            }
        });
    }
};

//src/widget/button-set-menu.js
/**
 * InputMenu构件
 * 可接受输入的下拉菜单构件
 */
_p[35] = {
    value: function(require) {
        var $ = _p.r(4), tpl = _p.r(25), InputButton = _p.r(43), Menu = _p.r(50), Mask = _p.r(49), Utils = _p.r(13);
        return _p.r(13).createClass("InputMenu", {
            base: _p.r(60),
            constructor: function(options) {
                var marker = Utils.getMarker();
                this.callBase(marker);
                var defaultOptions = {
                    input: null,
                    menu: null,
                    mask: null
                };
                this.__extendOptions(defaultOptions, options);
                this.widgetName = "InputMenu";
                this.__tpl = tpl;
                // 最后输入时间
                this.__lastTime = 0;
                // 最后选中的记录
                this.__lastSelect = null;
                this.__inputWidget = null;
                this.__menuWidget = null;
                this.__maskWidget = null;
                // menu状态， 记录是否已经append到dom树上
                this.__menuState = false;
                if (options !== marker) {
                    this.__render();
                }
            },
            select: function(index) {
                this.__menuWidget.select(index);
            },
            setValue: function(value) {
                this.__inputWidget.setValue(value);
                return this;
            },
            getValue: function() {
                return this.__inputWidget.getValue();
            },
            __render: function() {
                if (this.__rendered) {
                    return this;
                }
                this.__inputWidget = new InputButton(this.__options.input);
                this.__menuWidget = new Menu(this.__options.menu);
                this.__maskWidget = new Mask(this.__options.mask);
                this.callBase();
                this.__inputWidget.appendTo(this.__element);
                this.__menuWidget.positionTo(this.__inputWidget);
                this.__initInputMenuEvent();
            },
            open: function() {
                this.__maskWidget.show();
                this.__menuWidget.show();
            },
            close: function() {
                this.__maskWidget.hide();
                this.__menuWidget.hide();
            },
            __initInputMenuEvent: function() {
                var _self = this;
                this.on("buttonclick", function() {
                    if (!this.__menuState) {
                        this.__appendMenu();
                        this.__menuState = true;
                    }
                    this.__inputWidget.unfocus();
                    this.open();
                });
                this.on("keypress", function(e) {
                    this.__lastTime = new Date();
                });
                this.on("keyup", function(e) {
                    if (e.keyCode !== 8 && e.keyCode !== 13 && new Date() - this.__lastTime < 500) {
                        this.__update();
                    }
                });
                this.on("inputcomplete", function() {
                    this.__inputWidget.selectRange(99999999);
                    this.__inputComplete();
                });
                this.__menuWidget.on("select", function(e, info) {
                    e.stopPropagation();
                    _self.setValue(info.value);
                    _self.trigger("select", info);
                    _self.close();
                });
                this.__menuWidget.on("change", function(e, info) {
                    e.stopPropagation();
                    _self.trigger("change", info);
                });
                // 阻止input自身的select和change事件
                this.__inputWidget.on("select change", function(e) {
                    e.stopPropagation();
                });
                // mask 点击关闭
                this.__maskWidget.on("maskclick", function() {
                    _self.close();
                });
                // 记录最后选中的数据
                this.on("select", function(e, info) {
                    this.__lastSelect = info;
                });
            },
            // 更新输入框内容
            __update: function() {
                var inputValue = this.getValue(), lowerCaseValue = inputValue.toLowerCase(), values = this.__getItemValues(), targetValue = null;
                if (!inputValue) {
                    return;
                }
                $.each(values, function(i, val) {
                    if (val.toLowerCase().indexOf(lowerCaseValue) === 0) {
                        targetValue = val;
                        return false;
                    }
                });
                if (targetValue) {
                    this.__inputWidget.setValue(targetValue);
                    this.__inputWidget.selectRange(inputValue.length);
                }
            },
            // 获取所有item的值列表
            __getItemValues: function() {
                var vals = [];
                $.each(this.__menuWidget.getWidgets(), function(index, item) {
                    vals.push(item.getValue());
                });
                return vals;
            },
            // 用户输入完成
            __inputComplete: function() {
                var values = this.__getItemValues(), targetIndex = -1, inputValue = this.getValue(), lastSelect = this.__lastSelect;
                $.each(values, function(i, val) {
                    if (val === inputValue) {
                        targetIndex = i;
                        return false;
                    }
                });
                this.trigger("select", {
                    index: targetIndex,
                    value: inputValue
                });
                if (!lastSelect || lastSelect.value !== inputValue) {
                    this.trigger("change", {
                        from: lastSelect || {
                            index: -1,
                            value: null
                        },
                        to: {
                            index: targetIndex,
                            value: inputValue
                        }
                    });
                }
            },
            __appendMenu: function() {
                this.__menuWidget.appendTo(this.__inputWidget.getElement().ownerDocument.body);
            }
        });
    }
};

//src/widget/button-set.js
/**
 * Buttonset对象
 * 通用按钮构件
 */
_p[36] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), ToggleButton = _p.r(59);
        return _p.r(13).createClass("Buttonset", {
            base: _p.r(51),
            constructor: function(options) {
                var defaultOptions = {
                    // 初始选中项, -1表示不选中任何项
                    selected: -1
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getButtons: function() {
                return this.getWidgets();
            },
            getButton: function(index) {
                return this.getWidgets()[index] || null;
            },
            getValue: function() {
                if (this.__currentIndex > -1) {
                    return this.__widgets[this.__currentIndex].getValue();
                }
                return null;
            },
            getSelectedIndex: function() {
                return this.__currentIndex;
            },
            appendButton: function() {
                return this.appendWidget.apply(this, arguments);
            },
            insertButton: function() {
                return this.insertWidget.apply(this, arguments);
            },
            select: function(indexOrWidget) {
                if (this.__options.disabled) {
                    return this;
                }
                if (indexOrWidget instanceof ToggleButton) {
                    indexOrWidget = $.inArray(indexOrWidget, this.__widgets);
                }
                if (indexOrWidget < 0) {
                    return this.clearSelect();
                }
                indexOrWidget = this.__widgets[indexOrWidget];
                this.__pressButton(indexOrWidget);
                return this;
            },
            selectByValue: function(value) {
                var values = this.__widgets.map(function(button) {
                    return button.getValue();
                });
                return this.select(values.indexOf(value));
            },
            clearSelect: function() {
                this.__pressButton(null);
                return this;
            },
            removeButton: function() {
                return this.removeWidget.apply(this, arguments);
            },
            insertWidget: function(index, widget) {
                var returnValue = this.callBase(index, widget);
                if (returnValue === null) {
                    return returnValue;
                }
                if (index <= this.__currentIndex) {
                    this.__currentIndex++;
                }
                if (index <= this.__prevIndex) {
                    this.__prevIndex++;
                }
                return returnValue;
            },
            removeWidget: function(widget) {
                var index = widget;
                if (typeof index !== "number") {
                    index = this.indexOf(widget);
                }
                widget = this.callBase(widget);
                if (index === this.__currentIndex) {
                    this.__currentIndex = -1;
                } else if (index < this.__currentIndex) {
                    this.__currentIndex--;
                }
                if (index === this.__prevIndex) {
                    this.__prevIndex = -1;
                } else if (index < this.__prevIndex) {
                    this.__prevIndex--;
                }
                return widget;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Buttonset";
                // 当前选中项
                this.__currentIndex = this.__options.selected;
                // 前一次选中项
                this.__prevIndex = -1;
            },
            __render: function() {
                this.callBase();
                $(this.__element).addClass(CONF.classPrefix + "buttonset");
                this.__initButtons();
                return this;
            },
            __initButtons: function() {
                var _self = this, buttonWidget = null;
                $.each(this.__options.buttons, function(index, buttonOption) {
                    buttonWidget = new ToggleButton($.extend({}, buttonOption, {
                        pressed: index === _self.__options.selected,
                        preventDefault: true
                    }));
                    // 切换
                    buttonWidget.__on("click", function(e) {
                        if (!_self.isDisabled()) {
                            _self.__pressButton(this);
                        }
                    });
                    buttonWidget.__on("change", function(e) {
                        // 阻止buton本身的事件向上冒泡
                        e.stopPropagation();
                    });
                    _self.appendButton(buttonWidget);
                });
            },
            /**
         * 按下指定按钮, 该方法会更新其他按钮的状态和整个button-set的状态
         * @param button
         * @private
         */
            __pressButton: function(button) {
                this.__prevIndex = this.__currentIndex;
                this.__currentIndex = this.indexOf(button);
                if (this.__currentIndex === this.__prevIndex) {
                    return;
                }
                if (button) {
                    button.press();
                }
                // 弹起其他按钮
                $.each(this.__widgets, function(i, otherButton) {
                    if (otherButton !== button) {
                        otherButton.bounce();
                    }
                });
                this.trigger("change", {
                    currentIndex: this.__currentIndex,
                    prevIndex: this.__prevIndex
                });
            },
            __valid: function(ele) {
                return ele instanceof ToggleButton;
            }
        });
    }
};

//src/widget/button.js
/**
 * Button对象
 * 通用按钮构件
 */
_p[37] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), buttonTpl = _p.r(19), Icon = _p.r(42), Label = _p.r(48);
        return _p.r(13).createClass("Button", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    label: null,
                    text: null,
                    icon: null,
                    // label相对icon的位置
                    layout: "right"
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getLabel: function() {
                return this.__labelWidget.getText();
            },
            setLabel: function(text) {
                return this.__labelWidget.setText(text);
            },
            getLabelWidget: function() {
                return this.__labelWidget;
            },
            getIconWidget: function() {
                return this.__iconWidget;
            },
            __render: function() {
                this.callBase();
                this.__iconWidget = new Icon(this.__options.icon);
                this.__labelWidget = new Label(this.__options.label);
                // layout
                switch (this.__options.layout) {
                  case "left":
                  /* falls through */
                    case "top":
                    this.__element.appendChild(this.__labelWidget.getElement());
                    this.__element.appendChild(this.__iconWidget.getElement());
                    break;

                  case "right":
                  /* falls through */
                    case "bottom":
                  /* falls through */
                    default:
                    this.__element.appendChild(this.__iconWidget.getElement());
                    this.__element.appendChild(this.__labelWidget.getElement());
                    break;
                }
                $(this.__element).addClass(CONF.classPrefix + "button-layout-" + this.__options.layout);
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Button";
                this.__tpl = buttonTpl;
                this.__iconWidget = null;
                this.__labelWidget = null;
                if (typeof this.__options.label !== "object") {
                    this.__options.label = {
                        text: this.__options.label
                    };
                }
                if (typeof this.__options.icon !== "object") {
                    this.__options.icon = {
                        img: this.__options.icon
                    };
                }
            },
            __initEvent: function() {
                this.callBase();
                this.on("click", function() {
                    this.__trigger("btnclick");
                });
            }
        });
    }
};

//src/widget/colorpicker.js
/**
 * 容器类： PPanel = Positioning Panel
 */
_p[38] = {
    value: function(require) {
        var Utils = _p.r(13), CONF = _p.r(12), Mask = _p.r(49), tpl = _p.r(20), $ = _p.r(4);
        return Utils.createClass("ColorPicker", {
            base: _p.r(54),
            constructor: function(options) {
                var defaultOptions = {
                    clearText: "",
                    commonText: "",
                    commonColor: [ [ "#ffffff", "#000000", "#eeece1", "#1f497d", "#4f81bd", "#c0504d", "#9bbb59", "#8064a2", "#4bacc6", "#f79646" ], [ "#f2f2f2", "#808080", "#ddd8c2", "#c6d9f1", "#dbe5f1", "#f2dbdb", "#eaf1dd", "#e5dfec", "#daeef3", "#fde9d9" ], [ "#d9d9d9", "#595959", "#c4bc96", "#8db3e2", "#b8cce4", "#e5b8b7", "#d6e3bc", "#ccc0d9", "#b6dde8", "#fbd4b4" ], [ "#bfbfbf", "#404040", "#938953", "#548dd4", "#95b3d7", "#d99594", "#c2d69b", "#b2a1c7", "#92cddc", "#fabf8f" ], [ "#a6a6a6", "#262626", "#4a442a", "#17365d", "#365f91", "#943634", "#76923c", "#5f497a", "#31849b", "#e36c0a" ], [ "#7f7f7f", "#0d0d0d", "#1c1a10", "#0f243e", "#243f60", "#622423", "#4e6128", "#3f3151", "#205867", "#974706" ] ],
                    standardText: "",
                    standardColor: [ "#c00000", "#ff0000", "#ffc000", "#ffff00", "#92d050", "#00b050", "#00b0f0", "#0070c0", "#002060", "#7030a0" ]
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            show: function() {
                if (!this.__inDoc) {
                    this.__inDoc = true;
                    this.appendTo(this.__element.ownerDocument.body);
                }
                this.__maskWidget.show();
                this.callBase();
                return this;
            },
            hide: function() {
                this.callBase();
                this.__maskWidget.hide();
                return this;
            },
            attachTo: function($obj) {
                var _self = this;
                $obj.on("click", function() {
                    _self.appendTo($obj.getElement().ownerDocument.body);
                    _self.positionTo($obj);
                    _self.show();
                });
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "ColorPicker";
                this.__contentElement = null;
                this.__maskWidget = null;
                this.__inDoc = false;
            },
            __render: function() {
                this.callBase();
                $(this.__element).addClass(CONF.classPrefix + "colorpicker");
                var contentHtml = Utils.Tpl.compile(tpl, this.__options);
                this.__contentElement.appendChild($(contentHtml)[0]);
                this.__previewElement = $(this.__contentElement).find("." + CONF.classPrefix + "colorpicker-preview");
                this.__clearElement = $(this.__contentElement).find("." + CONF.classPrefix + "colorpicker-clear");
                this.__maskWidget = new Mask(this.__options.mask);
            },
            // 初始化点击事件
            __initEvent: function() {
                var _self = this;
                this.callBase();
                this.on("click", function(e) {
                    var color, $target = $(e.target);
                    if ($target.hasClass(CONF.classPrefix + "colorpicker-item")) {
                        color = $target.attr("data-color");
                        _self.trigger("selectcolor", color);
                        _self.hide();
                    } else if ($target.hasClass(CONF.classPrefix + "colorpicker-clear")) {
                        _self.trigger("selectcolor", "");
                        _self.hide();
                    }
                });
                this.on("mouseover", function(e) {
                    var color, $target = $(e.target);
                    if ($target.hasClass(CONF.classPrefix + "colorpicker-item")) {
                        color = $target.attr("data-color");
                        $(_self.__element).find("." + CONF.classPrefix + "colorpicker-preview").css({
                            "background-color": color,
                            "border-color": color
                        });
                    }
                });
                this.__maskWidget.on("click", function() {
                    _self.hide();
                });
            }
        });
    }
};

//src/widget/container.js
/**
 * Container类， 所有容器类的父类`
 * @abstract
 */
_p[39] = {
    value: function(require) {
        var Utils = _p.r(13), CONF = _p.r(12), Widget = _p.r(60), Creator = _p.r(0), $ = _p.r(4);
        return Utils.createClass("Container", {
            base: Widget,
            constructor: function(options) {
                var defaultOptions = {
                    column: false,
                    widgets: null
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            indexOf: function(widget) {
                return $.inArray(widget, this.__widgets);
            },
            disable: function() {
                this.callBase();
                $.each(this.__widgets, function(index, widget) {
                    widget.disable();
                });
            },
            enable: function() {
                this.callBase();
                $.each(this.__widgets, function(index, widget) {
                    widget.enable();
                });
            },
            getWidgets: function() {
                return this.__widgets;
            },
            getWidget: function(index) {
                return this.__widgets[index] || null;
            },
            getWidgetByValue: function(value) {
                var widget = null;
                $.each(this.__widgets, function(i, wgt) {
                    if (wgt.getValue() === value) {
                        widget = wgt;
                        return false;
                    }
                });
                return widget;
            },
            appendWidget: function(widget) {
                if (!this.__valid(widget)) {
                    return null;
                }
                if (this.__options.disabled) {
                    widget.disable();
                }
                this.__widgets.push(widget);
                widget.appendTo(this.__contentElement);
                if (!this.__options.column) {
                    return widget;
                }
                if (this.__widgets.length > 0 && this.__widgets.length % this.__options.column === 0) {
                    this.__contentElement.appendChild($('<span class="fui-column"></span>')[0]);
                }
                $(widget.getElement()).addClass(CONF.classPrefix + "panel-column-widget");
                return widget;
            },
            appendWidgets: function(widgetArray) {
                var _self = this, widgets = widgetArray;
                if (!$.isArray(widgetArray)) {
                    widgets = arguments;
                }
                $.each(widgets, function(i, widget) {
                    _self.appendWidget(widget);
                });
                return this;
            },
            // TODO insertWidget时， 如果columnu为指定的数字，那么该插入需要调整换行符，现在还未对这个逻辑做处理。
            insertWidget: function(index, widget) {
                var oldElement = null;
                if (this.__widgets.length === 0) {
                    return this.appendWidget(widget);
                }
                if (!this.__valid(widget)) {
                    return null;
                }
                if (this.__options.disabled) {
                    widget.disable();
                }
                oldElement = this.__widgets[index];
                this.__widgets.splice(index, 0, widget);
                this.__contentElement.insertBefore(widget.getElement(), oldElement.getElement());
                if (this.__options.column === false) {
                    return widget;
                }
                this.__contentElement.insertBefore($('<span class="fui-column"></span>')[0], oldElement.getElement());
                $(widget.getElement()).addClass(CONF.classPrefix + "panel-column-widget");
                return widget;
            },
            insertWidgets: function(index, widgetArray) {
                var _self = this, widgets = widgetArray;
                if (!$.isArray(widgetArray)) {
                    widgets = [].slice.call(arguments, 1);
                }
                $.each(widgets, function(i, widget) {
                    _self.insertWidget(index, widget);
                    index++;
                });
                return this;
            },
            getContentElement: function() {
                return this.__contentElement;
            },
            removeWidget: function(widget) {
                if (typeof widget === "number") {
                    widget = this.__widgets.splice(widget, 1)[0];
                } else {
                    this.__widgets.splice(this.indexOf(widget), 1);
                }
                this.__contentElement.removeChild(widget.getElement());
                $(widget.getElement()).removeClass(CONF.classPrefix + "panel-column-widget");
                return widget;
            },
            __initOptions: function() {
                this.widgetName = "Container";
                this.__widgets = [];
                this.__contentElement = null;
                this.__options.column -= 0;
            },
            __render: function() {
                this.callBase();
                this.__contentElement = this.__element;
                $(this.__element).addClass(CONF.classPrefix + "container");
                if (this.__options.column) {
                    $(this.__element).addClass(CONF.classPrefix + "container-column");
                }
                return this;
            },
            // Override
            __appendChild: function(childWidget) {
                return this.appendWidget(childWidget);
            },
            __initWidgets: function() {
                if (!this.__options.widgets) {
                    return;
                }
                var widgets = Creator.parse(this.__options.widgets), _self = this;
                if (!$.isArray(widgets)) {
                    widgets = [ widgets ];
                }
                $.each(widgets, function(i, widget) {
                    _self.appendWidget(widget);
                });
            },
            /**
         * 验证元素给定元素是否可以插入当前容器中
         * @param ele 需要验证的元素
         * @returns {boolean} 允许插入返回true, 否则返回false
         * @private
         */
            __valid: function(ele) {
                return ele instanceof Widget;
            }
        });
    }
};

//src/widget/dialog.js
/**
 * 容器类： PPanel = Positioning Panel
 */
_p[40] = {
    value: function(require) {
        var Utils = _p.r(13), CONF = _p.r(12), Widget = _p.r(60), Mask = _p.r(49), tpl = _p.r(21), Button = _p.r(37), LAYOUT = CONF.layout, $ = _p.r(4), ACTION = {
            CANCEL: "cancel",
            OK: "ok"
        };
        return Utils.createClass("Dialog", {
            base: _p.r(54),
            constructor: function(options) {
                var defaultOptions = {
                    layout: LAYOUT.CENTER,
                    caption: null,
                    resize: "height",
                    draggable: true,
                    // 是否包含close button
                    closeButton: true,
                    mask: {
                        color: "#000",
                        opacity: .2
                    },
                    prompt: false,
                    // 底部按钮
                    buttons: [ {
                        className: CONF.classPrefix + "xdialog-ok-btn",
                        action: "ok",
                        label: "确定"
                    }, {
                        className: CONF.classPrefix + "xdialog-cancel-btn",
                        action: "cancel",
                        label: "取消"
                    } ]
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            open: function() {
                this.__fire("open", function() {
                    this.show();
                });
                return this;
            },
            close: function() {
                this.__fire("close", function() {
                    this.hide();
                });
                return this;
            },
            getButtons: function() {
                return this.__buttons;
            },
            getButton: function(index) {
                return this.__buttons[index];
            },
            appendTo: function(container) {
                this.callBase(container);
                this.__maskWidget.appendTo(container);
                this.__inDoc = true;
                return this;
            },
            show: function() {
                if (!this.__target) {
                    this.__target = this.__element.ownerDocument.body;
                }
                if (!this.__inDoc) {
                    this.appendTo(this.__element.ownerDocument.body);
                }
                this.__maskWidget.show();
                this.callBase();
                this.__openState = true;
                return this;
            },
            hide: function() {
                this.callBase();
                this.__maskWidget.hide();
                this.__openState = false;
                return this;
            },
            toggle: function() {
                this.isOpen() ? this.close() : this.open();
                return this;
            },
            isOpen: function() {
                return this.__openState;
            },
            getHeadElement: function() {
                return this.__headElement;
            },
            getBodyElement: function() {
                return this.getContentElement();
            },
            getFootElement: function() {
                return this.__footElement;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Dialog";
                this.__target = this.__options.target;
                this.__layout = this.__options.layout;
                this.__inDoc = false;
                this.__hinting = false;
                this.__openState = false;
                this.__headElement = null;
                this.__bodyElement = null;
                this.__footElement = null;
                this.__maskWidget = null;
                this.__buttons = [];
                if (this.__target instanceof Widget) {
                    this.__target = this.__target.getElement();
                }
            },
            __render: function() {
                this.callBase();
                this.__innerTpl = Utils.Tpl.compile(tpl, this.__options);
                this.__contentElement.appendChild($(this.__innerTpl)[0]);
                $(this.__element).addClass(CONF.classPrefix + "dialog");
                this.__headElement = $(".fui-dialog-head", this.__contentElement)[0];
                this.__bodyElement = $(".fui-dialog-body", this.__contentElement)[0];
                this.__footElement = $(".fui-dialog-foot", this.__contentElement)[0];
                this.__maskWidget = new Mask(this.__options.mask);
                this.__contentElement = this.__bodyElement;
                if (this.__options.draggable) {
                    this.__initDraggable();
                }
                if (this.__options.closeButton) {
                    this.__initCloseButton();
                }
                this.__initButtons();
                this.__initMaskLint();
            },
            __action: function(type, btn) {
                switch (type) {
                  case ACTION.OK:
                    if (this.__triggerHandler(type) !== false) {
                        this.close();
                    }
                    break;

                  case ACTION.CANCEL:
                    this.__triggerHandler(type);
                    this.close();
                    break;
                }
            },
            __initButtons: function() {
                var _self = this, button = null, foot = this.__footElement;
                $.each(this.__options.buttons, function(index, buttonOption) {
                    button = new Button(buttonOption);
                    button.appendTo(foot);
                    _self.__buttons.push(button);
                });
            },
            __initEvent: function() {
                var _self = this;
                this.callBase();
                $([ this.__footElement, this.__headElement ]).on("btnclick", function(e, btn) {
                    _self.__action(btn.getOptions().action, btn);
                });
                if (this.__options.prompt) {
                    $(this.__element).on("keydown", function(e) {
                        switch (e.keyCode) {
                          case 13:
                            _self.__action(ACTION.OK);
                            break;

                          case 27:
                            _self.__action(ACTION.CANCEL);
                            break;
                        }
                    });
                }
            },
            __initDraggable: function() {
                Utils.createDraggable({
                    handler: this.__headElement,
                    target: this.__element
                }).bind();
            },
            __initCloseButton: function() {
                var closeButton = new Button({
                    className: "fui-close-button",
                    action: "cancel",
                    icon: {
                        className: "fui-close-button-icon"
                    }
                });
                closeButton.appendTo(this.__headElement);
            },
            __initMaskLint: function() {
                var _self = this;
                this.__maskWidget.on("click", function() {
                    _self.__hint();
                });
            },
            __hint: function() {
                if (this.__hinting) {
                    return;
                }
                this.__hinting = true;
                var $ele = $(this.__element), _self = this, classNmae = [ CONF.classPrefix + "mask-hint", CONF.classPrefix + "mask-animate" ];
                $ele.addClass(classNmae.join(" "));
                window.setTimeout(function() {
                    $ele.removeClass(classNmae[0]);
                    window.setTimeout(function() {
                        $ele.removeClass(classNmae[1]);
                        _self.__hinting = false;
                    }, 200);
                }, 200);
            }
        });
    }
};

//src/widget/drop-panel.js
/**
 * DropPanel对象
 * 可接受输入的按钮构件
 */
_p[41] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), tpl = _p.r(22), Button = _p.r(37), Panel = _p.r(51), PPanel = _p.r(54), Mask = _p.r(49);
        return _p.r(13).createClass("DropPanel", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    button: null,
                    panel: null,
                    width: null,
                    height: null
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            disable: function() {
                this.callBase();
            },
            enable: function() {
                this.callBase();
            },
            open: function() {
                this.__popupWidget.appendWidget(this.__panelWidget);
                this.__maskWidget.show();
                this.__popupWidget.show();
                var $popup = $(this.__popupWidget.getElement());
                $popup.css("top", parseInt($popup.css("top")) - $(this.__element).outerHeight());
                $popup.css("min-width", $(this.__element).outerWidth());
                $popup.css("min-height", $(this.__element).height());
            },
            close: function() {
                this.__maskWidget.hide();
                this.__popupWidget.hide();
                this.__panelWidget.appendTo(this.__contentElement);
            },
            getPanelElement: function() {
                return this.__panelWidget.getElement();
            },
            appendWidget: function(widget) {
                this.__panelWidget.appendWidget(widget);
            },
            getWidgets: function() {
                return this.__panelWidget.getWidgets();
            },
            getWidget: function(index) {
                return this.__panelWidget.getWidget(index);
            },
            appendWidgets: function(widgets) {
                this.__panelWidget.appendWidgets.apply(this, arguments);
                return this;
            },
            insertWidget: function(index, widget) {
                this.__panelWidget.insertWidget(index, widget);
            },
            insertWidgets: function(index, widgets) {
                this.__panelWidget.insertWidgets.apply(this, arguments);
                return this;
            },
            removeWidget: function(widget) {
                return this.__panelWidget.removeWidget(widget);
            },
            __render: function() {
                this.__initOptions();
                this.__buttonWidget = new Button(this.__options.button);
                this.__panelWidget = new Panel(this.__options.content);
                this.__popupWidget = new PPanel();
                this.__maskWidget = new Mask(this.__options.mask);
                this.callBase();
                this.__popupWidget.positionTo(this.__element);
                $(this.__popupWidget.getElement()).addClass(CONF.classPrefix + "drop-panel-popup");
                // 初始化content
                var $content = $('<div class="' + CONF.classPrefix + 'drop-panel-content"></div>').append(this.__panelWidget.getElement());
                this.__contentElement = $content[0];
                // 插入按钮到element
                $(this.__element).append($content).append(this.__buttonWidget.getElement());
                this.__initDropPanelEvent();
            },
            __initOptions: function() {
                this.widgetName = "DropPanel";
                this.__tpl = tpl;
                this.__buttonWidget = null;
                this.__popupWidget = null;
                this.__panelWidget = null;
                this.__contentElement = null;
                this.__maskWidget = null;
                this.__popupState = false;
                if (typeof this.__options.button !== "object") {
                    this.__options.input = {
                        icon: this.__options.button
                    };
                }
            },
            __initDropPanelEvent: function() {
                var _self = this;
                this.__buttonWidget.on("click", function() {
                    if (!_self.__popupState) {
                        _self.__appendPopup();
                        _self.__popupState = true;
                    }
                    _self.trigger("buttonclick");
                    _self.open();
                });
                this.__panelWidget.on("click", function() {
                    _self.trigger("panelclick");
                });
                // mask 点击关闭
                this.__maskWidget.on("maskclick", function() {
                    _self.close();
                });
            },
            __appendPopup: function() {
                this.__popupWidget.appendTo(this.__element.ownerDocument.body);
            }
        });
    }
};

//src/widget/icon.js
/**
 * icon widget
 * 封装多种icon方式
 */
_p[42] = {
    value: function(require) {
        var $ = _p.r(4), iconTpl = _p.r(23);
        return _p.r(13).createClass("Icon", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    img: null
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getValue: function() {
                return this.__options.value || this.__options.img;
            },
            setImage: function(imageSrc) {
                if (this.__options.img === imageSrc) {
                    return this;
                }
                if (this.__image) {
                    this.__image.src = imageSrc;
                }
                this.trigger("iconchange", {
                    prevImage: this.__prevIcon,
                    currentImage: this.__currentIcon
                });
            },
            getImage: function() {
                return this.__currentIcon;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Icon";
                this.__tpl = iconTpl;
                this.__prevIcon = null;
                this.__currentIcon = this.__options.img;
                this.__image = null;
            },
            __render: function() {
                this.__options.__width = this.__options.width;
                this.__options.__height = this.__options.height;
                this.__options.width = null;
                this.__options.height = null;
                this.callBase();
                if (!this.__options.img) {
                    return;
                }
                this.__image = $("img", this.__element)[0];
                if (this.__options.__width !== null) {
                    this.__image.width = this.__options.__width;
                }
                if (this.__options.__height !== null) {
                    this.__image.height = this.__options.__height;
                }
            }
        });
    }
};

//src/widget/input-button.js
/**
 * InputButton对象
 * 可接受输入的按钮构件
 */
_p[43] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), tpl = _p.r(24), Button = _p.r(37), Input = _p.r(45);
        return _p.r(13).createClass("InputButton", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    button: null,
                    input: null,
                    placeholder: null,
                    // label相对icon的位置
                    layout: "right"
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getValue: function() {
                return this.__inputWidget.getValue();
            },
            setValue: function(value) {
                this.__inputWidget.setValue(value);
                return this;
            },
            reset: function() {
                this.__inputWidget.reset();
            },
            selectAll: function() {
                this.__inputWidget.selectAll();
                return this;
            },
            selectRange: function(start, end) {
                this.__inputWidget.selectRange(start, end);
                return this;
            },
            focus: function() {
                this.__inputWidget.focus();
                return this;
            },
            unfocus: function() {
                this.__inputWidget.unfocus();
                return this;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "InputButton";
                this.__tpl = tpl;
                this.__inputWidget = null;
                this.__buttonWidget = null;
                if (typeof this.__options.input !== "object") {
                    this.__options.input = {
                        placeholder: this.__options.input
                    };
                }
                this.__options.input = $.extend({}, this.__options.input, {
                    placeholder: this.__options.placeholder
                });
                if (typeof this.__options.button !== "object") {
                    this.__options.button = {
                        icon: this.__options.button
                    };
                }
            },
            __render: function() {
                var _self = this;
                this.callBase();
                this.__buttonWidget = new Button(this.__options.button);
                this.__inputWidget = new Input(this.__options.input);
                // layout
                switch (this.__options.layout) {
                  case "left":
                  /* falls through */
                    case "top":
                    this.__buttonWidget.appendTo(this.__element);
                    this.__inputWidget.appendTo(this.__element);
                    break;

                  case "right":
                  /* falls through */
                    case "bottom":
                  /* falls through */
                    default:
                    this.__inputWidget.appendTo(this.__element);
                    this.__buttonWidget.appendTo(this.__element);
                    break;
                }
                $(this.__element).addClass(CONF.classPrefix + "layout-" + this.__options.layout);
                this.__buttonWidget.on("click", function() {
                    _self.trigger("buttonclick");
                });
            }
        });
    }
};

//src/widget/input-menu.js
/**
 * InputMenu构件
 * 可接受输入的下拉菜单构件
 */
_p[44] = {
    value: function(require) {
        var $ = _p.r(4), tpl = _p.r(25), InputButton = _p.r(43), Menu = _p.r(50), Mask = _p.r(49);
        return _p.r(13).createClass("InputMenu", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    input: null,
                    menu: null,
                    mask: null,
                    selected: -1
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            select: function(index) {
                this.__menuWidget.select(index);
            },
            selectByValue: function(value) {
                return this.__selectBy("values", value);
            },
            selectByLabel: function(value) {
                return this.__selectBy("labels", value);
            },
            clearSelect: function() {
                this.__lastSelect = -1;
                this.__menuWidget.clearSelect();
                this.__inputWidget.reset();
                return this;
            },
            setValue: function(value) {
                return this;
            },
            getValue: function() {
                return this.__menuWidget.getSelectedItem().getValue();
            },
            open: function() {
                this.__maskWidget.show();
                this.__menuWidget.show();
            },
            close: function() {
                this.__maskWidget.hide();
                this.__menuWidget.hide();
            },
            __render: function() {
                this.__inputWidget = new InputButton(this.__options.input);
                this.__menuWidget = new Menu(this.__options.menu);
                this.__maskWidget = new Mask(this.__options.mask);
                this.callBase();
                this.__inputWidget.appendTo(this.__element);
                this.__menuWidget.positionTo(this.__inputWidget);
                this.__initInputValue();
            },
            __selectBy: function(type, value) {
                var values = this.__getItemValues()[type], index = -1;
                $.each(values, function(i, val) {
                    if (value === val) {
                        index = i;
                        return false;
                    }
                });
                if (index !== -1) {
                    this.select(index);
                    return this.__menuWidget.getSelectedItem();
                }
                return null;
            },
            __initInputValue: function() {
                var selectedItem = this.__menuWidget.getItem(this.__options.selected);
                if (selectedItem) {
                    this.__inputWidget.setValue(selectedItem.getLabel());
                }
            },
            __initEvent: function() {
                var _self = this;
                this.callBase();
                this.on("buttonclick", function() {
                    if (!this.__menuState) {
                        this.__appendMenu();
                        this.__menuState = true;
                    }
                    this.__inputWidget.unfocus();
                    this.open();
                });
                this.on("keypress", function(e) {
                    this.__lastTime = new Date();
                });
                this.on("keyup", function(e) {
                    if (e.keyCode !== 8 && e.keyCode !== 13 && new Date() - this.__lastTime < 500) {
                        this.__update();
                    }
                });
                this.on("inputcomplete", function() {
                    this.__inputWidget.selectRange(99999999);
                    this.__inputComplete();
                });
                this.__menuWidget.on("select", function(e, info) {
                    e.stopPropagation();
                    _self.__inputWidget.setValue(info.label);
                    _self.trigger("select", info);
                    _self.close();
                });
                this.__menuWidget.on("menuitemclick", function(e, info) {
                    _self.trigger("itemclick", info);
                });
                this.__menuWidget.on("change", function(e, info) {
                    e.stopPropagation();
                    _self.trigger("change", info);
                });
                // 阻止input自身的select和change事件
                this.__inputWidget.on("select change", function(e) {
                    e.stopPropagation();
                });
                // mask 点击关闭
                this.__maskWidget.on("maskclick", function() {
                    _self.close();
                });
                // 记录最后选中的数据
                this.on("select", function(e, info) {
                    this.__lastSelect = info;
                });
            },
            // 更新输入框内容
            __update: function() {
                var inputValue = this.__inputWidget.getValue(), lowerCaseValue = inputValue.toLowerCase(), values = this.__getItemValues().labels, targetValue = null;
                if (!inputValue) {
                    return;
                }
                $.each(values, function(i, val) {
                    if (val.toLowerCase().indexOf(lowerCaseValue) === 0) {
                        targetValue = val;
                        return false;
                    }
                });
                if (targetValue) {
                    this.__inputWidget.setValue(targetValue);
                    this.__inputWidget.selectRange(inputValue.length);
                }
            },
            // 获取所有item的值列表
            __getItemValues: function() {
                var vals = [], labels = [];
                $.each(this.__menuWidget.getWidgets(), function(index, item) {
                    labels.push(item.getLabel());
                    vals.push(item.getValue());
                });
                return {
                    labels: labels,
                    values: vals
                };
            },
            // 用户输入完成
            __inputComplete: function() {
                var itemsInfo = this.__getItemValues(), labels = itemsInfo.labels, targetIndex = -1, inputValue = this.__inputWidget.getValue(), lastSelect = this.__lastSelect;
                $.each(labels, function(i, label) {
                    if (label === inputValue) {
                        targetIndex = i;
                        return false;
                    }
                });
                this.trigger("select", {
                    index: targetIndex,
                    label: inputValue,
                    value: itemsInfo.values[targetIndex]
                });
                if (!lastSelect || lastSelect.value !== inputValue) {
                    this.trigger("change", {
                        from: lastSelect || {
                            index: -1,
                            label: null,
                            value: null
                        },
                        to: {
                            index: targetIndex,
                            label: inputValue,
                            value: itemsInfo.values[targetIndex]
                        }
                    });
                }
            },
            __appendMenu: function() {
                this.__menuWidget.appendTo(this.__inputWidget.getElement().ownerDocument.body);
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "InputMenu";
                this.__tpl = tpl;
                // 最后输入时间
                this.__lastTime = 0;
                // 最后选中的记录
                this.__lastSelect = null;
                this.__inputWidget = null;
                this.__menuWidget = null;
                this.__maskWidget = null;
                // menu状态， 记录是否已经append到dom树上
                this.__menuState = false;
                if (this.__options.selected !== -1) {
                    this.__options.menu.selected = this.__options.selected;
                }
            }
        });
    }
};

//src/widget/input.js
/*jshint camelcase:false*/
/**
* Input widget
*/
_p[45] = {
    value: function(require) {
        var CONF = _p.r(12), $ = _p.r(4), tpl = _p.r(26);
        return _p.r(13).createClass("Input", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    placeholder: null
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getValue: function() {
                return this.__element.value;
            },
            setValue: function(value) {
                this.__element.value = value;
                return this;
            },
            disable: function() {
                this.callBase();
                this.__element.disabled = true;
            },
            enable: function() {
                this.__element.disabled = false;
                this.callBase();
            },
            reset: function() {
                this.__element.value = this.__options.value || "";
                return this;
            },
            selectAll: function() {
                this.__element.select();
            },
            selectRange: function(startIndex, endIndex) {
                if (!startIndex) {
                    startIndex = 0;
                }
                if (!endIndex) {
                    endIndex = 1e9;
                }
                this.__element.setSelectionRange(startIndex, endIndex);
            },
            focus: function() {
                this.__element.focus();
                return this;
            },
            unfocus: function() {
                this.__element.blur();
                return this;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Input";
                this.__tpl = tpl;
                // input构件允许获得焦点
                this.__allow_focus = true;
            },
            __render: function() {
                this.callBase();
                this.__element.removeAttribute("unselectable");
                if (this.__options.placeholder) {
                    this.__element.setAttribute("placeholder", this.__options.placeholder);
                }
                this.addClass(CONF.classPrefix + "selectable");
            },
            __initEvent: function() {
                this.callBase();
                this.on("keydown", function(e) {
                    if (e.keyCode === 13) {
                        this.trigger("inputcomplete", {
                            value: this.getValue()
                        });
                    }
                });
            }
        });
    }
};

//src/widget/item.js
/**
 * Label Widget
 */
_p[46] = {
    value: function(require) {
        var Utils = _p.r(13), itemTpl = _p.r(27), Icon = _p.r(42), Label = _p.r(48), CONF = _p.r(12), $ = _p.r(4);
        return Utils.createClass("Item", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    label: "",
                    icon: null,
                    selected: false,
                    textAlign: "left"
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getValue: function() {
                return this.__options.value || this.__labelWidget.getValue() || this.__iconWidget.getValue() || null;
            },
            select: function() {
                this.__update(true);
                return this;
            },
            unselect: function() {
                this.__update(false);
                return this;
            },
            isSelected: function() {
                return this.__selectState;
            },
            setLabel: function(text) {
                this.__labelWidget.setText(text);
                return this;
            },
            getLabel: function() {
                return this.__labelWidget.getText();
            },
            setIcon: function(imageSrc) {
                this.__iconWidget.setImage(imageSrc);
                return this;
            },
            getIcon: function() {
                return this.__iconWidget.getImage();
            },
            __render: function() {
                this.callBase();
                this.__iconWidget = new Icon(this.__options.icon);
                this.__labelWidget = new Label(this.__options.label);
                this.__iconWidget.appendTo(this.__element);
                this.__labelWidget.appendTo(this.__element);
            },
            __update: function(state) {
                var fn = state ? "addClass" : "removeClass";
                state = !!state;
                $(this.__element)[fn](CONF.classPrefix + "item-selected");
                this.__selectState = state;
                this.trigger(state ? "itemselect" : "itemunselect");
                return this;
            },
            __initEvent: function() {
                this.callBase();
                this.on("click", function() {
                    this.trigger("itemclick");
                });
            },
            /**
         * 初始化模板所用的css值
         * @private
         */
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Item";
                this.__tpl = itemTpl;
                this.__iconWidget = null;
                this.__labelWidget = null;
                this.__selectState = this.__options.selected;
                if (typeof this.__options.label !== "object") {
                    this.__options.label = {
                        text: this.__options.label
                    };
                }
                if (!this.__options.label.textAlign) {
                    this.__options.label.textAlign = this.__options.textAlign;
                }
                if (typeof this.__options.icon !== "object") {
                    this.__options.icon = {
                        img: this.__options.icon
                    };
                }
            }
        });
    }
};

//src/widget/label-panel.js
/**
 * LabelPanel Widget
 * 带标签的面板
 */
_p[47] = {
    value: function(require) {
        var Utils = _p.r(13), CONF = _p.r(12), Label = _p.r(48), $ = _p.r(4);
        return Utils.createClass("LabelPanel", {
            base: _p.r(51),
            constructor: function(options) {
                var defaultOptions = {
                    layout: "bottom"
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            disable: function() {
                this.callBase();
                this.__labelWidget.disable();
            },
            enable: function() {
                this.callBase();
                this.__labelWidget.enable();
            },
            __render: function() {
                var $contentElement = null, opts = this.__options, ele = this.__element, classPrefix = CONF.classPrefix, labelClass = "fui-label-panel-content", originEle = null;
                this.__labelWidget = new Label(opts.label);
                this.callBase();
                originEle = this.__contentElement;
                $(ele).addClass(classPrefix + "label-panel");
                $(ele).addClass(classPrefix + "layout-" + opts.layout);
                $contentElement = $('<div class="' + labelClass + '"></div>');
                originEle.appendChild(this.__labelWidget.getElement());
                originEle.appendChild($contentElement[0]);
                // 更新contentElement
                this.__contentElement = $contentElement[0];
                return this;
            },
            __initOptions: function() {
                var label = this.__options.label;
                this.callBase();
                this.widgetName = "LabelPanel";
                this.__labelWidget = null;
                if (typeof label !== "object") {
                    this.__options.label = {
                        text: label
                    };
                }
                if (!this.__options.label.className) {
                    this.__options.label.className = "";
                }
                this.__options.label.className += " fui-label-panel-label";
            }
        });
    }
};

//src/widget/label.js
/**
 * Label Widget
 */
_p[48] = {
    value: function(require) {
        var Utils = _p.r(13), labelTpl = _p.r(28), $ = _p.r(4);
        return Utils.createClass("Label", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    text: "",
                    textAlign: "center"
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getValue: function() {
                return this.__options.text;
            },
            setText: function(text) {
                var oldtext = this.__options.text;
                this.__options.text = text;
                $(this.__element).text(text);
                this.trigger("labelchange", {
                    currentText: text,
                    prevText: oldtext
                });
                return this;
            },
            getText: function() {
                return this.__options.text;
            },
            // label 禁用title显示
            __allowShowTitle: function() {
                return false;
            },
            /**
         * 初始化模板所用的css值
         * @private
         */
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Label";
                this.__tpl = labelTpl;
                this.__options.text = this.__options.text.toString();
            }
        });
    }
};

//src/widget/mask.js
/*jshint camelcase:false*/
/**
 * Mask Widget
 */
_p[49] = {
    value: function(require) {
        var Utils = _p.r(13), tpl = _p.r(29), Widget = _p.r(60), $ = _p.r(4), __cache_inited = false, __MASK_CACHE = [];
        return Utils.createClass("Mask", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    bgcolor: "#000",
                    opacity: 0,
                    inner: true,
                    target: null,
                    // 禁止mouse scroll事件
                    scroll: false,
                    hide: true
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            maskTo: function(target) {
                if (target) {
                    this.__target = target;
                }
                return this;
            },
            show: function() {
                var docNode = null;
                if (!this.__target) {
                    this.__target = this.__element.ownerDocument.body;
                }
                docNode = this.__target.ownerDocument.documentElement;
                // 如果节点未添加到dom树， 则自动添加到文档的body节点上
                if (!$.contains(docNode, this.__element)) {
                    this.appendTo(this.__target.ownerDocument.body);
                }
                this.callBase();
                this.__position();
                this.__resize();
                this.__hideState = false;
            },
            hide: function() {
                this.callBase();
                this.__hideState = true;
            },
            isHide: function() {
                return this.__hideState;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Mask";
                this.__tpl = tpl;
                this.__cacheId = __MASK_CACHE.length;
                this.__hideState = true;
                __MASK_CACHE.push(this);
                this.__target = this.__options.target;
                if (this.__target instanceof Widget) {
                    this.__target = this.__target.getElement();
                }
            },
            __render: function() {
                this.callBase();
                this.__initMaskEvent();
                if (!__cache_inited) {
                    __cache_inited = true;
                    __initCacheEvent();
                }
            },
            __initMaskEvent: function() {
                this.on("mousewheel", function(e) {
                    var evt = e.originalEvent;
                    e.preventDefault();
                    e.stopPropagation();
                    this.trigger("scroll", {
                        delta: evt.wheelDelta || evt.deltaY || evt.detail
                    });
                });
                this.on("click", function(e) {
                    e.stopPropagation();
                    if (e.target === this.__element) {
                        this.trigger("maskclick");
                    }
                });
            },
            // 定位
            __resize: function() {
                var targetRect = null;
                // body特殊处理
                if (this.__targetIsBody()) {
                    targetRect = $(Utils.getView(this.__target));
                    targetRect = {
                        width: targetRect.width(),
                        height: targetRect.height()
                    };
                } else {
                    targetRect = Utils.getRect(this.__target);
                }
                this.__element.style.width = targetRect.width + "px";
                this.__element.style.height = targetRect.height + "px";
            },
            __position: function() {
                var location = null, targetRect = null;
                if (this.__targetIsBody()) {
                    location = {
                        top: 0,
                        left: 0
                    };
                } else {
                    targetRect = Utils.getRect(this.__target);
                    location = {
                        top: targetRect.top,
                        left: targetRect.left
                    };
                }
                $(this.__element).css("top", location.top + "px").css("left", location.left + "px");
            },
            __targetIsBody: function() {
                return this.__target.tagName.toLowerCase() === "body";
            }
        });
        // 全局监听
        function __initCacheEvent() {
            $(window).on("resize", function() {
                $.each(__MASK_CACHE, function(i, mask) {
                    if (mask && !mask.isHide()) {
                        mask.__resize();
                    }
                });
            });
        }
    }
};

//src/widget/menu.js
/**
 * Menu Widget
 */
_p[50] = {
    value: function(require) {
        var Utils = _p.r(13), Item = _p.r(46), CONF = _p.r(12), $ = _p.r(4);
        return Utils.createClass("Menu", {
            base: _p.r(54),
            constructor: function(options) {
                var defaultOptions = {
                    column: true,
                    selected: -1,
                    textAlign: "left",
                    items: []
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            select: function(index) {
                var item = this.__widgets[index];
                if (!item) {
                    return this;
                }
                this.__selectItem(item);
                return this;
            },
            clearSelect: function() {
                var selectedItem = this.getSelectedItem();
                if (selectedItem) {
                    selectedItem.unselect();
                }
                this.__currentSelect = -1;
                this.__prevSelect = -1;
            },
            getItems: function() {
                return this.getWidgets.apply(this, arguments);
            },
            getItem: function() {
                return this.getWidget.apply(this, arguments);
            },
            appendItem: function(item) {
                return this.appendWidget.apply(this, arguments);
            },
            insertItem: function(item) {
                return this.insertWidget.apply(this, arguments);
            },
            removeItem: function(item) {
                return this.removeWidget.apply(this, arguments);
            },
            clearItems: function() {
                while (this.getItems().length) {
                    this.removeItem(0);
                }
                return this;
            },
            getSelected: function() {
                return this.__currentSelect;
            },
            getSelectedItem: function() {
                return this.getItem(this.__currentSelect);
            },
            insertWidget: function(index, widget) {
                var returnValue = this.callBase(index, widget);
                if (returnValue === null) {
                    return returnValue;
                }
                if (index <= this.__currentSelect) {
                    this.__currentSelect++;
                }
                if (index <= this.__prevSelect) {
                    this.__prevSelect++;
                }
                return returnValue;
            },
            removeWidget: function(widget) {
                var index = widget;
                if (typeof index !== "number") {
                    index = this.indexOf(widget);
                }
                widget = this.callBase(widget);
                if (index === this.__currentSelect) {
                    this.__currentSelect = -1;
                } else if (index < this.__currentSelect) {
                    this.__currentSelect--;
                }
                if (index === this.__prevSelect) {
                    this.__prevSelect = -1;
                } else if (index < this.__prevSelect) {
                    this.__prevSelect--;
                }
                return widget;
            },
            __initOptions: function() {
                this.callBase();
                this.__prevSelect = -1;
                this.__currentSelect = this.__options.selected;
                this.widgetName = "Menu";
            },
            __render: function() {
                var _self = this, textAlign = this.__options.textAlign, selected = this.__options.selected;
                this.callBase();
                $(this.__element).addClass(CONF.classPrefix + "menu");
                $.each(this.__options.items, function(index, itemOption) {
                    if (typeof itemOption !== "object") {
                        itemOption = {
                            label: itemOption
                        };
                    }
                    itemOption.selected = index === selected;
                    itemOption.textAlign = textAlign;
                    _self.appendItem(new Item(itemOption));
                });
            },
            // 初始化点击事件
            __initEvent: function() {
                this.callBase();
                this.on("itemclick", function(e) {
                    this.__selectItem(e.widget, true);
                });
            },
            __selectItem: function(item, isUserTrigger) {
                var info = null;
                if (this.__currentSelect > -1) {
                    this.__widgets[this.__currentSelect].unselect();
                }
                this.__prevSelect = this.__currentSelect;
                this.__currentSelect = this.indexOf(item);
                item.select();
                info = {
                    index: this.__currentSelect,
                    label: item.getLabel(),
                    value: item.getValue()
                };
                if (isUserTrigger) {
                    this.trigger("menuitemclick", info);
                }
                this.trigger("select", info);
                if (this.__prevSelect !== this.__currentSelect) {
                    var fromItem = this.__widgets[this.__prevSelect] || null;
                    this.trigger("change", {
                        from: {
                            index: this.__prevSelect,
                            label: fromItem && fromItem.getLabel(),
                            value: fromItem && fromItem.getValue()
                        },
                        to: {
                            index: this.__currentSelect,
                            label: item.getLabel(),
                            value: item.getValue()
                        }
                    });
                }
            },
            __valid: function(target) {
                return target instanceof Item;
            }
        });
    }
};

//src/widget/panel.js
/**
 * 容器类： Panel
 */
_p[51] = {
    value: function(require) {
        var Utils = _p.r(13), panelTpl = _p.r(30), $ = _p.r(4);
        return Utils.createClass("Panel", {
            base: _p.r(39),
            constructor: function(options) {
                var defaultOptions = {};
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            __render: function() {
                var $content = null;
                this.callBase();
                $content = $('<div class="fui-panel-content"></div>');
                this.__contentElement.appendChild($content[0]);
                this.__contentElement = $content[0];
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Panel";
                this.__tpl = panelTpl;
            }
        });
    }
};

//src/widget/popup-menu.js
_p[52] = {
    value: function(require) {
        var Utils = _p.r(13), CONF = _p.r(12), $ = _p.r(4), Menu = _p.r(50);
        return Utils.createClass("PopupMenu", {
            base: _p.r(53),
            constructor: function(options) {
                this.callBase($.extend({
                    menu: {}
                }, options));
            },
            getMenuWidget: function() {
                return this.__menuWidget;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "PopupMenu";
            },
            __render: function() {
                this.callBase();
                this.__menuWidget = new Menu();
                this.appendWidget(this.__menuWidget);
                $(this.__element).addClass(CONF.classPrefix + "popup-menu");
            }
        });
    }
};

//src/widget/popup.js
/**
 * 容器类： PPanel = Positioning Panel
 */
_p[53] = {
    value: function(require) {
        var Utils = _p.r(13), CONF = _p.r(12), Widget = _p.r(60), Mask = _p.r(49), $ = _p.r(4);
        return Utils.createClass("Popup", {
            base: _p.r(54),
            constructor: function(options) {
                var defaultOptions = {
                    mask: {}
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            open: function() {
                this.__fire("open", function() {
                    this.show();
                });
                return this;
            },
            close: function() {
                this.__fire("close", function() {
                    this.hide();
                });
                return this;
            },
            show: function() {
                if (!this.__target) {
                    this.__target = this.__element.ownerDocument.body;
                }
                if (!this.__inDoc) {
                    this.__inDoc = true;
                    this.appendTo(this.__element.ownerDocument.body);
                }
                this.__maskWidget.show();
                this.callBase();
                this.__openState = true;
                return this;
            },
            hide: function() {
                this.callBase();
                this.__maskWidget.hide();
                this.__openState = false;
                return this;
            },
            toggle: function() {
                this.isOpen() ? this.close() : this.open();
                return this;
            },
            isOpen: function() {
                return this.__openState;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Popup";
                this.__target = this.__options.target;
                this.__layout = this.__options.layout;
                this.__inDoc = false;
                this.__openState = false;
                this.__maskWidget = null;
                if (this.__target instanceof Widget) {
                    this.__target = this.__target.getElement();
                }
            },
            __render: function() {
                this.callBase();
                $(this.__element).addClass(CONF.classPrefix + "popup");
                this.__maskWidget = new Mask(this.__options.mask);
                if (this.__options.draggable) {
                    this.__initDraggable();
                }
                this.__initMaskEvent();
            },
            __initMaskEvent: function() {
                var _self = this;
                this.__maskWidget.on("click", function() {
                    _self.close();
                });
            }
        });
    }
};

//src/widget/ppanel.js
/*jshint camelcase:false*/
/**
 * 容器类： PPanel = Positioning Panel
 */
_p[54] = {
    value: function(require) {
        var Utils = _p.r(13), CONF = _p.r(12), Widget = _p.r(60), LAYOUT = CONF.layout, $ = _p.r(4);
        return Utils.createClass("PPanel", {
            base: _p.r(51),
            constructor: function(options) {
                var defaultOptions = {
                    layout: LAYOUT.BOTTOM,
                    target: null,
                    // 边界容器
                    bound: null,
                    // 和边界之间的最小距离
                    diff: 10,
                    hide: true,
                    resize: "all",
                    iframe: false
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            positionTo: function(target, layout) {
                if (target instanceof Widget) {
                    target = target.getElement();
                }
                this.__target = target;
                if (layout) {
                    this.__layout = layout;
                }
                return this;
            },
            show: function() {
                var docNode = null;
                if (!this.__target) {
                    return this.callBase();
                }
                if (!this.__options.bound) {
                    this.__options.bound = this.__target.ownerDocument.body;
                }
                docNode = this.__target.ownerDocument.documentElement;
                if (!$.contains(docNode, this.__element)) {
                    this.__target.ownerDocument.body.appendChild(this.__element);
                }
                if ($.contains(docNode, this.__target)) {
                    this.callBase(Utils.getMarker());
                    this.__position();
                    this.__resize();
                }
                return this;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "PPanel";
                this.__target = this.__options.target;
                this.__layout = this.__options.layout;
                // 记录是否已调整过高度
                this.__height_resized = false;
            },
            __render: function() {
                this.callBase();
                if (this.__options.iframe) {
                    $('<iframe class="fui-ppanel-cover" frameborder="0"></iframe>').appendTo(this.__element);
                }
                $(this.__element).addClass(CONF.classPrefix + "ppanel");
            },
            // 执行定位
            __position: function() {
                var location = null, className = CONF.classPrefix + "ppanel-position";
                $(this.__element).addClass(className);
                location = this.__getLocation();
                $(this.__element).css("top", location.top + "px").css("left", location.left + "px");
            },
            __resize: function() {
                var targetRect = Utils.getBound(this.__target);
                switch (this.__options.resize) {
                  case "all":
                    this.__resizeWidth(targetRect);
                    this.__resizeHeight();
                    break;

                  case "width":
                    this.__resizeWidth(targetRect);
                    break;

                  case "height":
                    this.__resizeHeight();
                    break;
                }
            },
            /**
         * 在未指定宽度的情况下，执行自动宽度适配。
         * 如果构件未被指定宽度， 则添加一个最小宽度， 该最小宽度等于给定目标的宽度
         * @param targetRect 传递该参数，是出于整体性能上的考虑。
         * @private
         */
            __resizeWidth: function(targetRect) {
                if (!this.__target) {
                    return;
                }
                var $ele = $(this.__element), w = $ele.outerWidth(), h = $ele.outerHeight(), minWidth = targetRect.width - w - h;
                this.__element.style.minWidth = minWidth + "px";
            },
            /**
         * 调整panel高度，使其不超过边界范围，如果已设置高度， 则不进行调整
         * @private
         */
            __resizeHeight: function() {
                var boundRect = null, panelRect = null, diff = 0;
                panelRect = Utils.getRect(this.__element);
                panelRect.height = this.__element.scrollHeight;
                panelRect.bottom = panelRect.top + panelRect.height;
                boundRect = this.__getBoundRect();
                diff = panelRect.bottom - boundRect.bottom;
                if (diff > 0) {
                    this.__height_resized = true;
                    diff = panelRect.height - diff - this.__options.diff;
                    $(this.__element).css("height", diff + "px");
                } else if (this.__height_resized) {
                    this.__element.style.height = null;
                }
            },
            __getLocation: function() {
                var targetRect = Utils.getBound(this.__target);
                switch (this.__layout) {
                  case LAYOUT.CENTER:
                  case LAYOUT.MIDDLE:
                    return this.__getCenterLayout(targetRect);

                  case LAYOUT.LEFT:
                  case LAYOUT.RIGHT:
                  case LAYOUT.BOTTOM:
                  case LAYOUT.TOP:
                    return this.__getOuterLayout(targetRect);

                  default:
                    return this.__getInnerLayout(targetRect);
                }
                return location;
            },
            /**
         * 居中定位的位置属性
         * @private
         */
            __getCenterLayout: function(targetRect) {
                var location = {
                    top: 0,
                    left: 0
                }, panelRect = Utils.getRect(this.__element), diff = 0;
                diff = targetRect.height - panelRect.height;
                if (diff > 0) {
                    location.top = targetRect.top + diff / 2;
                }
                diff = targetRect.width - panelRect.width;
                if (diff > 0) {
                    location.left = targetRect.left + diff / 2;
                }
                return location;
            },
            /**
         * 获取外部布局定位属性
         * @returns {{top: number, left: number}}
         * @private
         */
            __getOuterLayout: function(targetRect) {
                var location = {
                    top: 0,
                    left: 0
                }, panelRect = Utils.getRect(this.__element);
                switch (this.__layout) {
                  case LAYOUT.TOP:
                    location.left = targetRect.left;
                    location.top = targetRect.top - panelRect.height;
                    break;

                  case LAYOUT.LEFT:
                    location.top = targetRect.top;
                    location.left = targetRect.left - panelRect.width;
                    break;

                  case LAYOUT.RIGHT:
                    location.top = targetRect.top;
                    location.left = targetRect.right;
                    break;

                  case LAYOUT.BOTTOM:
                  /* falls through */
                    default:
                    location.left = targetRect.left;
                    location.top = targetRect.bottom;
                    break;
                }
                return location;
            },
            /**
         * 获取内部布局定位属性,并且，内部布局还拥有根据水平空间的大小，自动进行更新定位的功能
         * @private
         */
            __getInnerLayout: function(targetRect) {
                var location = {
                    top: 0,
                    left: 0
                }, rect = targetRect, panelRect = Utils.getRect(this.__element);
                switch (this.__layout) {
                  case LAYOUT.LEFT_TOP:
                    location.top = rect.top;
                    location.left = rect.left;
                    break;

                  case LAYOUT.RIGHT_TOP:
                    location.top = rect.top;
                    location.left = rect.left + rect.width - panelRect.width;
                    break;

                  case LAYOUT.LEFT_BOTTOM:
                    location.top = rect.top + rect.height - panelRect.height;
                    location.left = rect.left;
                    break;

                  case LAYOUT.RIGHT_BOTTOM:
                    location.top = rect.top + rect.height - panelRect.height;
                    location.left = rect.left + rect.width - panelRect.width;
                    break;
                }
                return this.__correctionLocation(location);
            },
            __getBoundRect: function() {
                var width = -1, height = -1, view = null;
                if (this.__options.bound.tagName.toLowerCase() === "body") {
                    view = Utils.getView(this.__options.bound);
                    width = $(view).width();
                    height = $(view).height();
                    return {
                        top: 0,
                        left: 0,
                        right: width,
                        bottom: height,
                        width: width,
                        height: height
                    };
                } else {
                    return Utils.getRect(this.__options.bound);
                }
            },
            // 如果发生“溢出”，则修正定位
            __correctionLocation: function(location) {
                var panelRect = Utils.getRect(this.__element), targetRect = Utils.getRect(this.__target), boundRect = this.__getBoundRect();
                switch (this.__layout) {
                  case LAYOUT.LEFT_TOP:
                  case LAYOUT.LEFT_BOTTOM:
                    if (location.left + panelRect.width > boundRect.right) {
                        location.left += targetRect.width - panelRect.width;
                    }
                    break;

                  case LAYOUT.RIGHT_TOP:
                  case LAYOUT.RIGHT_BOTTOM:
                    if (location.left < boundRect.left) {
                        location.left = targetRect.left;
                    }
                    break;
                }
                return location;
            }
        });
    }
};

//src/widget/select-menu.js
/**
 * SelectMenu构件
 * 提供从下拉菜单中选中某一项的功能构件
 */
_p[55] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), Panel = _p.r(51), PPanel = _p.r(54), Button = _p.r(37), Creator = _p.r(0), Mask = _p.r(49);
        return _p.r(13).createClass("SelectMenu", {
            base: Panel,
            constructor: function(options) {
                var defaultOptions = {
                    // bed 是Panel的实例
                    bed: {
                        className: "fui-select-menu-bed"
                    },
                    button: {
                        className: "fui-select-menu-btn"
                    },
                    events: [ "btnclick" ],
                    selected: -1
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            show: function() {
                this.__widgetMenu.show();
                this.__mask.show();
            },
            hide: function() {
                this.__widgetMenu.hide();
                this.__mask.hide();
            },
            getValue: function() {
                var selectedWidet = this.__widgets[this.__selected];
                return selectedWidet ? selectedWidet.getValue() : null;
            },
            getSelected: function() {
                return this.__widgets[this.__selected];
            },
            select: function(index) {
                var widget = this.__widgets[index], className = "fui-select-menu-selected", oldIndex = this.__selected, oldSelected = this.__widgets[this.__selected], bedElement = this.__bed.getElement();
                if (!widget) {
                    return this;
                }
                if (oldSelected) {
                    oldSelected.removeClass(className);
                }
                bedElement.innerHTML = "";
                bedElement.appendChild(widget.cloneElement());
                widget.addClass(className);
                this.__selected = index;
                this.trigger("select", {
                    index: index,
                    widget: widget
                });
                if (this.__selected !== oldIndex) {
                    this.trigger("change", {
                        from: {
                            index: oldIndex,
                            widget: oldSelected
                        },
                        to: {
                            index: index,
                            widget: widget
                        }
                    });
                }
                return this;
            },
            selectByWidget: function(widget) {
                return this.select(this.indexOf(widget));
            },
            selectByValue: function(value) {
                var index = -1;
                $.each(this.__widgets, function(i, widget) {
                    if (widget.getValue() === value) {
                        index = i;
                        return false;
                    }
                });
                return this.select(index);
            },
            __render: function() {
                this.__bed = new Panel(this.__options.bed);
                this.__dropBtn = new Button(this.__options.button);
                this.__widgetMenu = new PPanel({
                    className: "fui-select-menu-container",
                    layout: CONF.layout.LEFT_TOP,
                    column: this.__column
                });
                this.__mask = new Mask();
                var widgets = this.__initWidgets();
                this.callBase();
                $(this.__element).addClass(CONF.classPrefix + "select-menu");
                this.appendWidgets([ this.__bed, this.__dropBtn, this.__mask, this.__widgetMenu ]);
                this.__widgets = [];
                this.__oldContentElement = this.__contentElement;
                this.__contentElement = this.__widgetMenu;
                this.appendWidgets(widgets);
                this.__widgetMenu.positionTo(this.getElement());
                if (!$.isNumeric(this.__options.selected)) {
                    this.__bed.appendWidget(Creator.parse(this.__options.selected));
                } else if (this.__options.selected != -1) {
                    this.select(this.__options.selected);
                }
            },
            __initWidgets: function() {
                var widgets = [];
                if (!this.__options.widgets) {
                    return;
                }
                $.each(this.__options.widgets, function(index, widget) {
                    widgets.push(Creator.parse(widget));
                });
                return widgets;
            },
            __initEvent: function() {
                this.callBase();
                var _self = this;
                this.__dropBtn.on("btnclick", function() {
                    _self.show();
                });
                this.__mask.on("click", function() {
                    _self.hide();
                });
                this.__widgetMenu.on(this.__options.events.join(" "), function(e) {
                    var target = e.target;
                    if (target === _self.__oldContentElement || target === _self.getElement) {
                        return;
                    }
                    e.stopPropagation();
                    _self.selectByWidget(e.widget);
                    _self.hide();
                    return false;
                });
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "SelectMenu";
                // 被选中的元素
                this.__bed = null;
                // 下拉按钮
                this.__dropBtn = null;
                this.__widgetMenu = null;
                this.__mask = null;
                this.__widgets = [];
                this.__selected = -1;
                this.__oldContentElement = null;
                this.__column = this.__options.column;
                delete this.__options.column;
            }
        });
    }
};

//src/widget/separator.js
/**
 * Separator(分隔符) Widget
 */
_p[56] = {
    value: function(require) {
        var Utils = _p.r(13), separatorTpl = _p.r(31), $ = _p.r(4);
        return Utils.createClass("Separator", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    width: 1,
                    height: "100%",
                    bgcolor: "#e1e1e1"
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Separator";
                this.__tpl = separatorTpl;
            }
        });
    }
};

//src/widget/spin-button.js
/**
 * SpinButton对象
 * 数值按钮构件
 */
_p[57] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), tpl = _p.r(32), Button = _p.r(37), Input = _p.r(45), Panel = _p.r(51);
        return _p.r(13).createClass("SpinButton", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    suffix: null,
                    selected: -1,
                    items: []
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getValue: function() {
                return this.__options.items[this.__currentSelected] || null;
            },
            // Overload
            setValue: function(value) {},
            select: function(index) {
                this.__update(index);
            },
            // 根据值进行选择
            selectByValue: function(value) {
                value = value + "";
                this.__update($.inArray(value, this.__options.items));
            },
            __render: function() {
                this.callBase();
                this.__buttons = [ new Button({
                    className: CONF.classPrefix + "spin-up-btn"
                }), new Button({
                    className: CONF.classPrefix + "spin-down-btn"
                }) ];
                this.__inputWidget = new Input();
                this.__panelWidget = new Panel({
                    column: true
                });
                this.__inputWidget.appendTo(this.__element);
                this.__panelWidget.appendWidget(this.__buttons[0]);
                this.__panelWidget.appendWidget(this.__buttons[1]);
                this.__panelWidget.appendTo(this.__element);
                this.__initSelected(this.__options.selected);
            },
            __initEvent: function() {
                var _self = this;
                this.callBase();
                this.__buttons[0].on("btnclick", function() {
                    _self.__update(_self.__currentSelected - 1);
                });
                this.__buttons[1].on("btnclick", function() {
                    _self.__update(_self.__currentSelected + 1);
                });
            },
            __initSelected: function(index) {
                this.__update(index, false);
            },
            __update: function(index, isTrigger) {
                var oldIndex = -1, value = null, toValue = null;
                if (index < 0 || index >= this.__options.items.length) {
                    return;
                }
                oldIndex = this.__currentSelected;
                this.__currentSelected = index;
                toValue = this.__options.items[this.__currentSelected];
                value = toValue + " " + (this.__options.suffix || "");
                this.__inputWidget.setValue(value);
                if (isTrigger !== false) {
                    this.trigger("change", {
                        from: this.__options.items[oldIndex] || null,
                        to: toValue
                    });
                }
            },
            __initOptions: function() {
                var items = this.__options.items;
                this.callBase();
                this.widgetName = "SpinButton";
                this.__tpl = tpl;
                this.__buttons = [];
                this.__panelWidget = null;
                this.__inputWidget = null;
                this.__currentSelected = -1;
                $.each(items, function(index, val) {
                    items[index] = val + "";
                });
            }
        });
    }
};

//src/widget/tabs.js
/**
 * Tabs Widget
 */
_p[58] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12), tpl = _p.r(33), Button = _p.r(37), Panel = _p.r(51);
        return _p.r(13).createClass("Tabs", {
            base: _p.r(60),
            constructor: function(options) {
                var defaultOptions = {
                    selected: 0,
                    buttons: [],
                    panels: []
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            getButtons: function() {
                return this.__btns;
            },
            getButton: function(index) {
                return this.getButtons()[index] || null;
            },
            getButtonByValue: function(value) {
                var button = null;
                $.each(this.__btns, function(i, btn) {
                    if (btn.getValue() === value) {
                        button = btn;
                        return false;
                    }
                });
                return button;
            },
            getPanels: function() {
                return this.__panels;
            },
            getPanel: function(index) {
                return this.getPanels()[index] || null;
            },
            getPanelByValue: function(value) {
                var panel = null;
                $.each(this.__panels, function(i, pan) {
                    if (pan.getValue() === value) {
                        panel = pan;
                        return false;
                    }
                });
                return panel;
            },
            getSelectedIndex: function() {
                return this.__selected;
            },
            getSelected: function() {
                var index = this.getSelectedIndex();
                return {
                    button: this.getButton(index),
                    panel: this.getPanel(index)
                };
            },
            appendTab: function(tabOpt) {
                tabOpt.panels = tabOpt.panels || [];
                return this.__renderByOptions(tabOpt);
            },
            removeTab: function(index) {
                if (index < 0) {
                    return null;
                }
                var btn = this.__btns.splice(index, 1), panel = this.__panels.splice(index, 1);
                if (!btn.length) {
                    return null;
                }
                btn = btn[0];
                panel = panel[0];
                btn.remove();
                panel.remove();
                return {
                    button: btn,
                    panel: panel
                };
            },
            /**
         * 选择接口
         * @param index 需要选中的tab页索引
         */
            select: function(index) {
                var toInfo = null;
                if (!this.__selectItem(index)) {
                    return this;
                }
                toInfo = this.__getInfo(index);
                this.trigger("tabsselect", toInfo);
                if (this.__prevSelected !== this.__selected) {
                    this.trigger("tabschange", {
                        from: this.__getInfo(this.__prevSelected),
                        to: toInfo
                    });
                }
                return this;
            },
            getIndexByButton: function(btn) {
                return $.inArray(btn, this.__btns);
            },
            /**
         * 把所有button追加到其他容器中
         */
            appendButtonTo: function(container) {
                $.each(this.__btns, function(index, btn) {
                    btn.appendTo(container);
                });
            },
            appendPanelTo: function(container) {
                $.each(this.__panels, function(index, panel) {
                    panel.appendTo(container);
                });
            },
            __render: function() {
                this.callBase();
                this.__btnWrap = $(".fui-tabs-button-wrap", this.__element)[0];
                this.__panelWrap = $(".fui-tabs-panel-wrap", this.__element)[0];
                this.__renderByOptions(this.__options);
                this.__selectItem(this.__options.selected);
            },
            __renderByOptions: function(options) {
                var _self = this, mapping = [], btns = this.__btns, panels = this.__panels, btnWrap = this.__btnWrap, panelWrap = this.__panelWrap;
                $.each(options.buttons, function(index, opt) {
                    var btn = null, panel = null;
                    if (typeof opt !== "object") {
                        opt = {
                            label: opt
                        };
                    }
                    btn = new Button(opt);
                    btn.on("click", function() {
                        _self.select(_self.getIndexByButton(this));
                    });
                    opt = options.panels[index] || {};
                    opt.hide = true;
                    panel = new Panel(opt);
                    btns.push(btn);
                    panels.push(panel);
                    mapping.push({
                        button: btn,
                        panel: panel
                    });
                    btn.appendTo(btnWrap);
                    panel.appendTo(panelWrap);
                });
                return mapping;
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "Tabs";
                this.__tpl = tpl;
                this.__btns = [];
                this.__panels = [];
                this.__prevSelected = -1;
                this.__selected = -1;
                this.__btnWrap = null;
                this.__panelWrap = null;
                // panels不设置的情况下， 将根据button创建
                if (this.__options.panels === null) {
                    this.__options.panels = [];
                    this.__options.panels.length = this.__options.buttons.length;
                }
            },
            __selectItem: function(index) {
                var btn = this.getButton(index), prevBtn = this.getButton(this.__selected), className = CONF.classPrefix + "selected";
                if (!btn) {
                    return false;
                }
                if (prevBtn) {
                    prevBtn.removeClass(className);
                    this.getPanel(this.__selected).hide();
                }
                btn.addClass(className);
                this.getPanel(index).show();
                this.__prevSelected = this.__selected;
                this.__selected = index;
                return true;
            },
            // 根据给定的tab索引获取先关的信息， 这些信息将用于事件携带的参数
            __getInfo: function(index) {
                return {
                    index: index,
                    button: this.getButton(index),
                    panel: this.getPanel(index)
                };
            }
        });
    }
};

//src/widget/toggle-button.js
/**
 * ToggleButton对象
 * 可切换按钮构件
 */
_p[59] = {
    value: function(require) {
        var $ = _p.r(4), CONF = _p.r(12);
        return _p.r(13).createClass("ToggleButton", {
            base: _p.r(37),
            constructor: function(options) {
                var defaultOptions = {
                    // 按钮初始时是否按下
                    pressed: false
                };
                options = $.extend({}, defaultOptions, options);
                this.callBase(options);
            },
            /**
         * 当前按钮是否已按下
         */
            isPressed: function() {
                return this.__state;
            },
            /**
         * 按下按钮
         */
            press: function() {
                var className = CONF.classPrefix + "button-pressed";
                $(this.__element).addClass(className);
                this.__updateState(true);
            },
            /**
         * 弹起按钮
         */
            bounce: function() {
                var className = CONF.classPrefix + "button-pressed";
                $(this.__element).removeClass(className);
                this.__updateState(false);
            },
            toggle: function() {
                if (this.__state) {
                    this.bounce();
                } else {
                    this.press();
                }
            },
            __initOptions: function() {
                this.callBase();
                this.widgetName = "ToggleButton";
                // 按钮当前状态
                this.__state = false;
            },
            __render: function() {
                this.callBase();
                $(this.__element).addClass(CONF.classPrefix + "toggle-button");
                this.__initButtonState();
                return this;
            },
            __initButtonState: function() {
                if (!this.__options.pressed) {
                    return;
                }
                // 不直接调用press方法， 防止初始化时事件的触发
                $(this.__element).addClass(CONF.classPrefix + "button-pressed");
                this.__state = true;
            },
            /**
         * 初始化事件监听, 控制状态的切换
         * @private
         */
            __initEvent: function() {
                this.callBase();
                this.on("click", function() {
                    this.toggle();
                });
            },
            __updateState: function(state) {
                state = !!state;
                this.__state = state;
                this.trigger("change", state, !state);
            }
        });
    }
};

//src/widget/widget.js
/*jshint camelcase:false*/
/**
 * widget对象
 * 所有的UI组件都是widget对象
 */
_p[60] = {
    value: function(require) {
        var prefix = "_fui_", uid = 0, CONF = _p.r(12), FUI_NS = _p.r(11), $ = _p.r(4), Utils = _p.r(13);
        var Widget = Utils.createClass("Widget", {
            constructor: function(options) {
                var defaultOptions = {
                    id: null,
                    className: "",
                    disabled: false,
                    preventDefault: false,
                    text: "",
                    value: null,
                    hide: false,
                    width: null,
                    height: null
                };
                this.__widgetType = "widget";
                this.__tpl = "";
                this.__compiledTpl = "";
                this.__options = {};
                this.__element = null;
                // 禁止获取焦点
                this.__allow_focus = !!CONF.allowFocus;
                this.widgetName = "Widget";
                this.__extendOptions(defaultOptions, options);
                this.__initOptions();
                this.__render();
                this.__initEvent();
                this.__initWidgets && this.__initWidgets();
            },
            getId: function() {
                return this.id;
            },
            getValue: function() {
                return this.__options.value;
            },
            getOptions: function() {
                return this.__options;
            },
            setValue: function(value) {
                this.__options.value = value;
                return this;
            },
            show: function() {
                this.__show();
                return this;
            },
            hide: function() {
                this.__hide();
                return this;
            },
            addClass: function(className) {
                $(this.__element).addClass(className);
                return this;
            },
            removeClass: function(className) {
                $(this.__element).removeClass(className);
                return this;
            },
            setStyle: function() {
                $.fn.css.apply($(this.__element), arguments);
                return this;
            },
            getStyle: function() {
                return $.fn.css.apply($(this.__element), arguments);
            },
            /**
         * 当前构件是否是处于禁用状态
         * @returns {boolean|disabled|jsl.$.disabled|id.disabled}
         */
            isDisabled: function() {
                return this.__options.disabled;
            },
            /**
         * 启用当前构件
         * @returns {Widget}
         */
            enable: function() {
                this.__options.disabled = false;
                $(this.__element).removeClass(CONF.classPrefix + "disabled");
                return this;
            },
            /**
         * 禁用当前构件
         * @returns {Widget}
         */
            disable: function() {
                this.__options.disabled = true;
                $(this.__element).addClass(CONF.classPrefix + "disabled");
                return this;
            },
            cloneElement: function() {
                return this.__element.cloneNode(true);
            },
            /**
         * 获取
         * @returns {null}
         */
            getElement: function() {
                return this.__element;
            },
            appendTo: function(container) {
                if (Utils.isElement(container)) {
                    container.appendChild(this.__element);
                } else if (container instanceof Widget) {
                    container.__appendChild(this);
                } else {
                    throw new Error("TypeError: Widget.appendTo()");
                }
                return this;
            },
            remove: function() {
                var parent = this.__element.parentNode;
                if (parent) {
                    parent.removeChild(this.__element);
                }
                return this;
            },
            off: function(type, cb) {
                $(this.__element).off(cb && cb.__fui_listener);
                return this;
            },
            on: function(type, cb) {
                if (!this.__options.preventDefault) {
                    this.__on(type, cb);
                }
                return this;
            },
            __initOptions: function() {},
            /**
         * 根据模板渲染构件, 如果该构件已经渲染过, 则不会进行二次渲染
         * @returns {Widget}
         */
            __render: function() {
                var $ele = null, tpl = this.__tpl, opts = this.__options, className = null;
                this.id = this.__id();
                // 向NS注册自己
                FUI_NS.__registerInstance(this);
                this.__compiledTpl = Utils.Tpl.compile(tpl, opts);
                this.__element = $(this.__compiledTpl)[0];
                this.__element.setAttribute("id", this.id);
                $ele = $(this.__element);
                if (opts.disabled) {
                    $ele.addClass(CONF.classPrefix + "disabled");
                }
                $ele.addClass(CONF.classPrefix + "widget");
                // add custom class-name
                className = opts.className;
                if (className.length > 0) {
                    if ($.isArray(className)) {
                        $ele.addClass(className.join(" "));
                    } else {
                        $ele.addClass(className);
                    }
                }
                this.__initBasicEnv();
                if (opts.hide) {
                    this.__hide();
                }
                if (opts.style) {
                    this.setStyle(opts.style);
                }
                return this;
            },
            /**
         * 该方法将被appendTo调用， 用于根据各组件自身的规则插入节点,  子类可根据需要覆盖该方法
         * @param childWidget 将被追加的子构件对象
         */
            __appendChild: function(childWidget) {
                return this.__element.appendChild(childWidget.getElement());
            },
            __initEvent: function() {
                this.on("mousedown", function(e) {
                    var tagName = e.target.tagName.toLowerCase();
                    if (!CONF.control[tagName] && !this.__allowFocus()) {
                        e.preventDefault();
                    } else {
                        e.stopPropagation();
                    }
                });
            },
            __on: function(type, cb) {
                var _self = this;
                cb.__fui_listener = function(e, widget) {
                    var params = [];
                    for (var i = 0, len = arguments.length; i < len; i++) {
                        if (i !== 1) {
                            params.push(arguments[i]);
                        }
                    }
                    e.widget = widget;
                    if (!_self.isDisabled()) {
                        return cb.apply(_self, params);
                    }
                };
                $(this.__element).on(type, cb.__fui_listener);
                return this;
            },
            trigger: function(type, params) {
                if (!this.__options.preventDefault) {
                    this.__trigger.apply(this, arguments);
                }
                return this;
            },
            __allowShowTitle: function() {
                return true;
            },
            __allowFocus: function() {
                return !!this.__allow_focus;
            },
            __trigger: function(type, params) {
                var args = [].slice.call(arguments, 1);
                $(this.__element).trigger(type, [ this ].concat(args));
                return this;
            },
            __triggerHandler: function(type, params) {
                var args = [ this ].concat([].slice.call(arguments, 1));
                return $(this.__element).triggerHandler(type, args);
            },
            /**
         * 同__trigger，都触发某事件，但是该方法触发的事件会主动触发before和after，
         * 同时如果before事件返回false，则后续handler都不会执行，且后续事件也不会再触发。
         * @param type 事件类型
         * @param handler 该事件所需要执行的函数句柄， 且该函数的返回值将作为该事件的参数发送给事件监听器
         * */
            __fire: function(type, handler) {
                var result = {
                    cancel: false
                };
                if (/^(before|after)/.test(type)) {
                    return this;
                }
                this.__trigger("before" + type, result);
                if (result.cancel === true) {
                    return this;
                }
                result = handler.call(this, type);
                this.__trigger(type);
                this.__trigger("after" + type, result);
                return this;
            },
            __extendOptions: function() {
                var args = [ {}, this.__options ], params = [ true ];
                args = args.concat([].slice.call(arguments, 0));
                for (var i = 0, len = args.length; i < len; i++) {
                    if (typeof args[i] !== "string") {
                        params.push(args[i]);
                    }
                }
                this.__options = $.extend.apply($, params);
            },
            __hide: function() {
                $(this.__element).addClass(CONF.classPrefix + "hide");
            },
            __show: function() {
                $(this.__element).removeClass(CONF.classPrefix + "hide");
            },
            __initBasicEnv: function() {
                if (this.__options.text && this.__allowShowTitle()) {
                    this.__element.setAttribute("title", this.__options.text);
                }
                if (this.__options.width) {
                    this.__element.style.width = this.__options.width + "px";
                }
                if (this.__options.height) {
                    this.__element.style.height = this.__options.height + "px";
                }
                if (this.widgetName) {
                    this.__element.setAttribute("rule", this.widgetName);
                }
            },
            __id: function() {
                return this.__options.id || generatorId();
            }
        });
        // 为widget生成唯一id
        function generatorId() {
            return prefix + ++uid;
        }
        return Widget;
    }
};

//dev-lib/exports.js
/**
 * 模块暴露
 */
_p[61] = {
    value: function(require) {
        _p.r(1);
        _p.r(2);
    }
};

var moduleMapping = {
    "fui.export": 61
};

function use(name) {
    _p.r([ moduleMapping[name] ]);
}
// 编译打包后的启动脚本
use( 'fui.export' );})();
/**
 * @fileOverview
 *
 * FIO 核心代码
 *
 * @author techird, Baidu FEX.
 *
 */

(function(Promise) {

    var fio = {
        version: '1.0'
    };

    /* 三个主要的命名空间 */
    fio.provider = {};
    fio.file = {};
    fio.user = {};


    /* IO 提供方列表 */
    var providerMap = {};

    /* FIO 当前使用的 IO 提供方 */
    var currentProvider = null;

    /* 返回值为空的 Promise */
    var noop = function() {
        return new Promise.resolve(null);
    };

    /* FIO 当前的用户系统实现 */
    var userImpl = {
        check: noop,
        login: noop,
        logout: noop,
        init: noop,
        current: noop
    };

    function inherit(sub, parent) {
        sub.prototype = Object.create(parent.prototype);
        sub.prototype.constructor = sub;
    }

    function AuthError() { Error.apply(this, arguments); }
    function FileRequestError(detail) {
        Error.call(this, detail);
        if (typeof(detail) == 'object') {
            for (var p in detail) {
                if (detail.hasOwnProperty(p)) this[p] = detail[p];
            }
        } else {
            this.detail = detail;
        }
    }

    inherit(AuthError, Error);
    inherit(FileRequestError, Error);

    fio.AuthError = AuthError;
    fio.FileRequestError = FileRequestError;


    /* 数据结构：表示一个用户 */
    function User(id, username) {
        this.id = id;
        this.username = username;
    }

    /* 数据结构：表示一份数据 */
    function Data(content) {
        this.content = content;

        if (content instanceof Blob) {
            this.type = fio.file.TYPE_BLOB;
        } else if (typeof(content) == 'string') {
            this.type = fio.file.TYPE_TEXT;
        } else if (typeof(content) == 'object') {
            this.type = fio.file.TYPE_JSON;
        } else {
            this.type = fio.file.TYPE_UNKNOWN;
        }
    }

    /* 数据结构：表示一个文件或目录 */
    function File(path) {
        this.setPath(path);
        this.isDir = false;
        this.data = null;
        this.size = 0;
        this.createTime = new Date();
        this.modifyTime = new Date();
    }

    File.prototype.setPath = function(path) {
        fio.file.anlysisPath(path, this);
    };

    fio.file.anlysisPath = function(path, fill) {
        fill = fill || {};

        var pathParts = path.split('/');

        // trim start
        while (pathParts[0] == '/' || pathParts[0] === '') {
            pathParts.shift();
        }

        // trim end
        while (pathParts[pathParts.length - 1] == '/' || pathParts[pathParts.length - 1] === '') {
            pathParts.pop();
        }

        fill.filename = pathParts.pop() || null;

        if (pathParts.length) {
            fill.parentPath = '/' + pathParts.join('/') + '/';
        } else {
            fill.parentPath = fill.filename ? '/' : null;
        }

        if (fill.filename) {
            var filenameParts = fill.filename.split('.');

            if (filenameParts.length > 1) {
                fill.extension = '.' + filenameParts.pop();
            } else {
                fill.extension = null;
            }

            fill.name = filenameParts.join('.');
            fill.path = fill.parentPath + fill.filename;
        } else {
            fill.path = '/';
        }

        return fill;
    };

    /* 数据结构：表示一个访问控制列表记录 */
    function Acl(user, file, access) {
        this.user = user;
        this.file = file;
        this.access = access || 0;
    }

    /* 数据结构：表示一个文件操作请求 */
    function FileRequest(path, method, user) {
        this.path = path;
        this.method = method;
        this.user = user;
        this.dupPolicy = fio.file.DUP_FAIL;
        this.newPath = null;
        this.acl = null;
        this.extra = null;
        this.dataType = fio.file.TYPE_TEXT;
    }

    /* 暴露需要的数据结构 */
    fio.user.User = User;
    fio.file.Data = Data;
    fio.file.File = File;
    fio.file.Acl = Acl;
    fio.file.FileRequest = FileRequest;

    /* 数据类型常量枚举 */
    fio.file.TYPE_TEXT = 'text';
    fio.file.TYPE_JSON = 'json';
    fio.file.TYPE_BLOB = 'blob';
    fio.file.TYPE_UNKNOWN = 'unknown';

    /* 文件操作常量枚举 */
    fio.file.METHOD_READ = 'read';
    fio.file.METHOD_WRITE = 'write';
    fio.file.METHOD_LIST = 'list';
    fio.file.METHOD_MOVE = 'move';
    fio.file.METHOD_DELETE = 'delete';
    fio.file.METHOD_MKDIR = 'mkdir';
    fio.file.METHOD_ACL_READ = 'readAcl';
    fio.file.METHOD_ACL_WRITE = 'writeAcl';

    /* 文件重复处理策略枚举 */
    fio.file.DUP_OVERWRITE = 'overwrite';
    fio.file.DUP_FAIL = 'fail';
    fio.file.DUP_RENAME = 'rename';

    /* 权限枚举 */
    fio.file.ACCESS_PUBLIC = 0x0001;
    fio.file.ACCESS_READ = 0x0002;
    fio.file.ACCESS_WRITE = 0x0004;
    fio.file.ACCESS_CREATE = 0x0008;
    fio.file.ACCESS_DELETE = 0x0010;
    fio.file.ACCESS_ACL_READ = 0x0020;
    fio.file.ACCESS_ACL_WRITE = 0x0040;
    fio.file.ACCESS_ALL = 0xfffe;

    /**
     * 注册一个 IO 提供方
     *
     * @method fio.provider.register
     *
     * @grammer fio.provider.register(name, provider)
     *
     * @param  {string} name
     *     提供方的名称
     *
     * @param  {object} provider
     *     提供方的实现
     *
     *     provider.init(opt) {function(object)}
     *         提供方的初始化方法，客户调用 fio.provider.init() 的时候会调用
     *
     *     provider.handle(request) {function(fio.file.FileRequest)}
     *         提供方处理文件请求的方法，根据 request.method 的不同取值返回不同的 Promise：
     *
     *             取值为 `fio.file.METHOD_LIST` 返回 Promise<fio.file.File[]>
     *             取值为 `fio.file.METHOD_ACL_READ` 返回 Promise<fio.file.ACL[]>
     *             取值为  `fio.file.METHOD_ACL_WRITE` 返回 Promise<fio.file.ACL[]>
     *             其他取值返回 `Promise<fio.file.File>`
     *
     * @see #fio.file.FileRequest
     *
     * @example
     *
     * fio.provider.register('netdisk', {
     *
     *     init: function(opt) {
     *         // init provider
     *     },
     *
     *     handle: function(request) {
     *         // handle request
     *     }
     *
     * });
     *
     */
    fio.provider.register = function(name, provider) {
        providerMap[name] = provider;
        if (!currentProvider) currentProvider = provider;

        // implement check
        if (typeof(provider.handle) != 'function') {
            throw new Error('Not implement: provider.handle()');
        }
    };


    /**
     * 切换 FIO 使用的默认 IO 提供方
     *
     * @method fio.provider.use
     *
     * @grammar fio.provider.use(name)
     *
     * @param  {string} name
     *     要使用的提供方的名称
     */
    fio.provider.use = function(name) {
        currentProvider = providerMap[name];
    };

    /**
     * 初始化指定的 IO 提供方
     *
     * @param  {string} name
     *     要初始化的提供方的名称
     *
     * @param  {object} opt
     *     初始化选项
     */
    fio.provider.init = function(name, opt) {
        var provider = providerMap[name];
        if (provider && typeof(provider.init) == 'function') {
            return provider.init.call(provider, opt);
        }
        return null;
    };

    /**
     * 实现 FIO 用户系统
     *
     * @method fio.user.impl
     *
     * @grammar fio.user.impl(impl)
     *
     * @param  {object} impl
     *     实现的代码，需要实现的方法包括：
     *
     *     impl.init(opt): null
     *         用户系统需要初始化的入口
     *
     *     impl.check(): fio.user.User
     *         返回当前用户
     *
     *     impl.login(): Promise<fio.user.User>
     *         进行用户的登陆
     *
     *     impl.logout(): Promise<fio.user.User>
     *         登出当前用户
     *
     *     impl.current(): fio.user.User
     *         返回当前用户（如果已登录）
     *
     */
    fio.user.impl = function(impl) {
        userImpl = impl;
    };

    ['check', 'login', 'logout', 'init', 'current'].forEach(function(operation) {
        fio.user[operation] = function() {
            return userImpl[operation].apply(userImpl, arguments);
        };
    });

    /**
     * 读取文件
     *
     * @method fio.file.read
     *
     * @grammar fio.file.read(opt)
     *
     * @param {object} opt 选项
     *
     *     opt.path {string}
     *         读取的文件的路径
     *
     * @return {Promise<fio.file.File>} 读取的文件
     *
     * @example
     *
     * ```js
     * fio.file.read({
     *     path: 'a.txt'
     * }).then(function(file) {
     *     console.log(file.data.content);
     * }).catch(function(e) {
     *     console.log(e.message);
     * });
     * ```
     */

    /**
     * 写入文件
     *
     * @method fio.file.write
     *
     * @grammar fio.file.write(opt)
     *
     * @param  {object} opt 选项
     *
     *     opt.path {string}
     *         要写入文件的位置
     *
     *     opt.content {string|object|blob}
     *         要写入的文件的内容
     *
     *     opt.ondup {Enum}
     *         存在同名文件时采取的策略
     *
     * @return {Promise<fio.file.File>} 返回已写入的文件
     *
     * @example
     *
     * ```js
     * fio.file.write({
     *     path: 'hello.txt',
     *     content: 'hello, fio!'
     * }).then(function(file) {
     *     console.log('the file size is ' + file.size);
     * }).catch(function(e) {
     *     console.log(e);
     * });
     * ```
     */

    /**
     * 列出指定目录的文件
     *
     * @method fio.file.list
     *
     * @grammar fio.file.list(opt)
     *
     * @param  {object} opt 选项
     *     opt.path {string} 要列出文件的路径
     *
     * @return {Promise<fio.file.File[]>} 列出的文件列表
     *
     * @example
     *
     * ```js
     * fio.file.list({
     *     path: '/kityminder/'
     * }).then(function(files) {
     *     console.table(files);
     * });
     * ``
     */

    /**
     * 移动指定的文件
     *
     * @method fio.file.move
     *
     * @grammar fio.file.move(opt)
     *
     * @param  {object} opt 选项
     *     opt.path {string} 要移动的文件或目录的路径
     *     opt.newPath {string} 目标位置
     *
     * @return {Promise<fio.file.File>}
     *
     * @example
     *
     * ```js
     * fio.file.move({
     *     path: '/kityminder/a.xmind',
     *     newPath: '/kityminder/b.xmind'
     * }).then(function(file) {
     *     console.log('file moved to' + file.path);
     * });
     * ``
     */


    /**
     * 删除文件
     *
     * @method fio.file.delete
     *
     * @grammar fio.file.delete(opt)
     *
     * @param {object} opt 选项
     *
     *     opt.path {string}
     *         要删除的文件的路径
     *
     * @return {Promise<fio.file.File>} 读取的文件
     *
     * @example
     *
     * ```js
     * fio.file.delete({
     *     path: 'a.txt'
     * }).then(function(file) {
     *     console.log('file deleted: ' + file.path);
     * }).catch(function(e) {
     *     console.log(e.message);
     * });
     * ```
     */


    /**
     * 创建目录
     *
     * @method fio.file.mkdir
     *
     * @grammar fio.file.mkdir(opt)
     *
     * @param {object} opt 选项
     *
     *     opt.path {string}
     *         要创建的目录的路径
     *
     * @return {Promise<fio.file.File>} 已创建的目录
     *
     * @example
     *
     * ```js
     * fio.file.mkdir({
     *     path: '/kityminder/a'
     * }).then(function(file) {
     *     console.log('dir created: ' + file.path);
     * }).catch(function(e) {
     *     console.log(e.message);
     * });
     * ```
     */


    /**
     * 读取指定路径的 ACL
     *
     * @method fio.file.readAcl
     *
     * @grammar fio.file.readAcl(opt)
     *
     * @param {object} opt 选项
     *
     *     opt.path {string}
     *         要读取 ACL 的路径
     *
     * @return {Promise<fio.file.Acl[]>} 读取的 ACL 集合
     *
     * @example
     *
     * ```js
     * fio.file.readAcl({
     *     path: 'a.txt'
     * }).then(function(acl) {
     *     console.table(acl);
     * }).catch(function(e) {
     *     console.log(e.message);
     * });
     * ```
     */


    /**
     * 写入指定路径的 ACL
     *
     * @method fio.file.writeAcl
     *
     * @grammar fio.file.write(opt)
     *
     * @param {object} opt 选项
     *
     *     opt.path {string}
     *         要写入 ACL 的路径
     *     option.acl {object}
     *         要写入的 ACL（username => access）
     *
     * @return {Promise<fio.file.Acl[]>} 写入后 ACL 后，指定路径的 ACL 集合
     *
     * @example
     *
     * ```js
     * fio.file.writeAcl({
     *     path: 'a.txt',
     *     acl: {
     *         techird: fio.file.ACCESS_READ | fio.file.ACCESS_WRITE
     *     }
     * }).then(function(acl) {
     *     console.table(acl);
     * }).catch(function(e) {
     *     console.log(e.message);
     * });
     * ```
     */
    ['read', 'write', 'list', 'move', 'delete', 'mkdir', 'readAcl', 'writeAcl'].forEach(function(operation) {

        fio.file[operation] = function(opt) {
            return fio.user.check().then(function(user) {

                var provider = opt.provider ? providerMap[opt.provider] : currentProvider;
                var request = new FileRequest(opt.path, operation, user);

                if (operation == 'read') {
                    request.dataType = opt.dataType || fio.file.TYPE_TEXT;
                }

                if (operation == 'write') {
                    request.dupPolicy = opt.ondup;
                    request.data = new fio.file.Data(opt.content);
                    delete opt.ondup;
                }

                if (operation == 'move') {
                    request.dupPolicy = opt.ondup;
                    request.newPath = opt.newPath;
                    delete opt.newPath;
                }

                if (operation == 'writeAcl') {
                    request.acl = opt.acl;
                    delete opt.acl;
                }

                delete opt.provider;
                delete opt.path;

                request.extra = opt;

                var response = provider.handle(request);
                // 确保返回的是一个 Promise 对象
                return Promise.resolve(response);
            });
        };
    });

    // export
    window.fio = fio;
})(Promise);
/**
 * @fileOverview
 *
 * 为 FIO 提供百度第三方平台用户系统
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

/* global fio: true, jQuery: true */
(function(window, $) {
    /**
     * 保存应用的 Api Key
     */
    var apiKey;

    /**
     * 登录后会有 access_token，验证后保存的当前用户
     *
     * 因为 API 中的 access_token 都是下划线命名法，所以这里不用骆驼，免得混淆
     */
    var access_token, user;

    /**
     * 用到的 URL 地址
     */
    var urls = {
        /**
         * Baidu OAuth 2.0 授权地址
         */
        'authorize': 'https://openapi.baidu.com/oauth/2.0/authorize',

        /**
         * 用户信息查询 API
         */
        'getLoggedInUser': 'https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser',

        /**
         * 当前 URL
         */
        'current': window.location.href
    };

    /**
     * 提供方的初始化方法
     *
     * @param  {object} opt 选项
     *
     *     opt.apiKey {string} 应用的 api key
     *
     */
    function init(opt) {
        apiKey = opt.apiKey || apiKey;
    }

    /**
     * 网络请求
     */
    function ajax(opt) {
        return new Promise(function(resolve, reject) {
            $.ajax(opt).done(resolve).fail(reject);
        });
    }

    /**
     * 解析 URL 上传递的参数
     * @return {object}
     */
    function urlFragment() {
        var url = urls.current;
        var pattern = /[&\?#](\w+?)=([^&]+)/g;
        var fragment = {};
        var match;

        while ((match = pattern.exec(url))) fragment[match[1]] = match[2];

        return fragment;
    }

    /**
     * 从 Cookie 中读取应用对应的 access_key
     */
    function readAK() {
        var cookie = document.cookie;
        var pattern = new RegExp(apiKey + '_ak=(.*?)(;|$)');
        var match = pattern.exec(cookie);
        return match && decodeURIComponent(match[1]) || null;
    }

    /**
     * 写入 access_key 到 cookie
     */
    function writeAK(ak, remember) {
        var cookie = apiKey + '_ak=' + encodeURIComponent(ak);
        cookie += '; max-age=' + (remember || 60);
        document.cookie = cookie;
    }

    /**
     * 清空 cookie 中对应的 ak
     */
    function clearAK() {
        document.cookie = apiKey + '_ak=';
    }

    /**
     * 返回当前用户
     *
     * @return {fio.user.User}
     */
    function current() {
        return user;
    }

    /**
     * 检查用户登录状态
     *
     * @return {Promise<fio.user.User>}
     */
    function check() {

        // 缓存检测
        if (user && +new Date() - user.validateTime < 60 * 60 * 1000) return Promise.resolve(user);

        if (check.pendingRequest) return check.pendingRequest;

        var fragment = urlFragment();

        // 登录回调；会在参数上有 AK
        if (fragment.access_token) {

            // 把 AK 保存在 Cookie 里
            writeAK(fragment.access_token, fragment.state);

            // 清掉登录回调参数
            document.location.href = urls.current.substr(0, document.location.href.indexOf('#'));

            return (check.pendingRequest = new Promise(function() {}));

        }

        // 非登录回调，读取 AK
        else {

            // 尝试从 Cookie 读取 AK
            access_token = readAK();

            // 读取失败返回
            if (!access_token) return Promise.resolve(null);
        }

        function getUserInfo() {
            return new Promise(function(resolve, reject) {
                // 超时重试
                var resolved = false;
                var timeouts = [1000, 2000, 3000];
                var timer = 0;

                function request() {
                    clearTimeout(timer);
                    if (!resolved && timeouts.length) {
                        timer = setTimeout(request, timeouts.shift());
                    }
                    return ajax({
                        url: urls.getLoggedInUser,
                        data: {
                            access_token: access_token
                        },
                        dataType: 'jsonp'
                    }).then(function(ret) {
                        clearTimeout(timer);
                        if (!resolved) resolve(ret);
                        resolved = true;
                    });
                }
                request();
            });
        }

        // 使用 AK 获得用户信息
        return check.pendingRequest = getUserInfo().then(function(ret) {

            // 授权错误，可能是 AK 过时了
            if (ret.error_code) {
                access_token = null;
                clearAK();
                return null;
            }

            user = new fio.user.User(ret.uid, ret.uname);

            user.smallImage = 'http://tb.himg.baidu.com/sys/portraitn/item/' + ret.portrait;
            user.largeImage = 'http://tb.himg.baidu.com/sys/portrait/item/' + ret.portrait;
            user.access_token = access_token;
            user.validateTime = +new Date();

            check.pendingRequest = null;

            return user;
        });
    }

    /**
     * 登录，直接跳到百度授权登录页面
     *
     * @param  {Object} opt 登录选项
     *
     *     opt.force {boolean}
     *         表示是否强制显示登录面板，而不是自动登录。默认为 false
     *
     *     opt.remember {int}
     *         表示是否记住用户登录状态，值表示记住的时间（秒）
     */
    function login(opt) {
        window.location.href = urls.authorize + '?' + [
            'client_id=' + apiKey,
            'response_type=token',
            'scope=basic netdisk',
            'redirect_uri=' + (opt.redirectUrl || urls.current), // 调回到当前页面，check 的时候就能捕获 AK
            'display=page',
            'force_login=' + (opt && opt.force ? 1 : 0),
            'state=' + (opt.remember || 60) // remember second
        ].join('&');
        return new Promise(function() {}); // never fullfilled
    }

    /**
     * 注销
     * @return {[type]} [description]
     */
    function logout() {
        var logouted = user;
        user = null;
        access_token = null;
        clearAK();
        return Promise.resolve(logouted);
    }

    // 用户系统实现
    fio.user.impl({
        check: check,
        login: login,
        logout: logout,
        init: init,
        current: current
    });

})(window, jQuery);
/* global fio:true, jQuery: true */

/**
 *
 * @fileOverview
 *
 * 为 FIO 提供网盘 IO 支持
 *
 * @author techird
 *
 * 使用网盘的 IO，需要：
 *
 *     1. 在 http://dev.baidu.com 上创建应用。创建应用后，就有相应的 API Key
 *     2. 设置登录回调地址为使用的页面，设置位置：其他API -> 安全设置
 *     3. 申请 PCS API 权限
 *
 */

/* TODO: 脱离 jQuery 依赖 */
(function(window, $) {

    /**
     * 用到的 URL 地址
     */
    var urls = {

        /**
         * PCS API 接口
         *
         * @see http://developer.baidu.com/wiki/index.php?title=docs/pcs/rest/file_data_apis_list
         */
        'file': 'https://pcs.baidu.com/rest/2.0/pcs/file'
    };

    /**
     * 网络请求
     */
    function ajax(opt) {
        opt.cache = false;
        return new Promise(function(resolve, reject) {
            $.ajax(opt).done(resolve).fail(reject);
        });
    }

    /**
     * 延时执行
     */
    function wait(delay) {
        return new Promise(function(resolve) {
            setTimeout(function() {
                resolve();
            }, delay);
        });
    }

    // 转换 PCS 的文件数据为 fio.file.File
    function pcs2file(pcs_file) {
        var file = new fio.file.File(pcs_file.path);
        file.createTime = new Date(pcs_file.ctime * 1000);
        file.modifyTime = new Date(pcs_file.mtime * 1000);
        file.size = pcs_file.size;
        file.isDir = !!pcs_file.isdir;
        file.provider = 'netdisk';
        return file;
    }

    function getMeta(path) {
        var user = fio.user.current();
        var access_token = user && user.access_token;

        if (!access_token) throw new Error('Not Authorized');

        function request() {
            return ajax({
                url: urls.file,
                data: {
                    method: 'meta',
                    access_token: access_token,
                    path: path
                },
                dataType: 'json'
            })['catch'](function(e) {
                if (request.retry++ > 2) throw e;
                return new Promise(function(resolve) {
                    setTimeout(function() {
                        resolve(request());
                    }, 200 * request.retry);
                });
            });
        }

        request.retry = 0;

        return request();
    }

    // 根据文件请求分发处理
    function handle(request) {
        var user = fio.user.current();
        var access_token = user && user.access_token;

        if (!access_token) throw new Error('Not Authorized');

        var param = {};

        // 默认参数
        var opt = {
            url: urls.file,
            type: 'GET',
            dataType: 'JSON'
        };

        // 处理其他参数
        switch (request.method) {

            case fio.file.METHOD_ACL_READ:
            case fio.file.METHOD_ACL_WRITE:
                throw new Error('Not Supported File Request:' + request.method);

            case fio.file.METHOD_READ:
                opt.dataType = request.dataType;
                param.method = 'download';
                break;

            case fio.file.METHOD_WRITE:
                opt.type = 'POST';

                param.method = 'upload';
                param.ondup = request.dupPolicy == fio.file.DUP_OVERWRITE ? 'overwrite' : 'newcopy';

                var form = new FormData();
                if (request.data.type == fio.file.TYPE_BLOB) {
                    form.append('file', request.data.content);
                } else {
                    form.append('file', new Blob([request.data.content], {
                        type: 'text/plain'
                    }));
                }
                opt.data = form;
                opt.processData = false;
                opt.contentType = false;

                break;

            case fio.file.METHOD_LIST:
                param.method = 'list';
                break;

            case fio.file.METHOD_MKDIR:
                opt.type = 'POST';
                param.method = 'mkdir';
                break;

            case fio.file.METHOD_MOVE:
                opt.type = 'POST';
                param.method = 'move';
                param.to = request.newPath;
                if (request.dupPolicy == fio.file.DUP_RENAME) {
                    param.ondup = 'newcopy';
                }
                break;

            case fio.file.METHOD_DELETE:
                opt.type = 'POST';
                param.method = 'delete';
                break;
        }

        // 处理 path 参数
        if (request.method == fio.file.METHOD_MOVE) {
            param.from = request.path;
        } else {
            param.path = request.path;
        }

        // 参数拼接到 URL 中
        opt.url += '?' + $.param(param) + '&access_token=' + access_token;

        // 重试次数
        var retry = request.extra.retry === undefined ? 3 : parseInt(request.extra.retry, 10);
        var failed = 0;

        function tryRequest() {

            // 捕捉到错误后重试或抛异常
            function retryOrThrow(e) {
                window.console.warn('PCS Fail(' + (++failed) + '): ', {
                    request: request,
                    error: e
                });
                if (retry--) {
                    return wait(1000 * failed).then(tryRequest);
                } else {
                    e.requestMethod = request.method;
                    e.requestPath = request.path;
                    e.requestUser = request.user && request.user.username || null;
                    e.requestParam = param;
                    if (request.dataType)
                        e.requestDataType = request.dataType;
                    throw e;
                }
            }

            function success(response) {

                function meta2pcs(meta) {
                    return meta.list[0];
                }

                // 调用失败
                if (response.error_code) {
                    throw new fio.FileRequestError(response);
                }

                // 读取操作需要抓取文件元数据后返回
                if (request.method === fio.file.METHOD_READ) {

                    return getMeta(param.path).then(meta2pcs).then(pcs2file).then(function(file) {
                        file.data = new fio.file.Data(response);
                        return file;
                    });
                }

                // 列文件返回
                if (request.method == fio.file.METHOD_LIST) {
                    return response.list.map(pcs2file);
                }

                // 移动文件返回
                if (request.method == fio.file.METHOD_MOVE) {
                    return new Promise(function(resolve) {
                        setTimeout(function() {
                            resolve(getMeta(response.extra.list[0].to).then(meta2pcs).then(pcs2file));
                        }, 200);
                    });
                }

                // 删除文件返回
                if (request.method == fio.file.METHOD_DELETE) {
                    return new fio.file.File(request.path);
                }

                var file = pcs2file(response);

                // 写文件返回
                if (request.method === fio.file.METHOD_WRITE) {
                    file.data = request.data;
                }

                return file;
            }

            function fail($xhr) {
                var response = $xhr.responseText;

                var responseInfo = {
                    readyState: $xhr.readyState,
                    status: $xhr.status,
                    statusText: $xhr.statusText,
                    headers: $xhr.getAllResponseHeaders(),
                    responseText: response
                };

                if (response) try {
                    responseInfo.detail = JSON.parse(response);
                } catch (ignore) {}

                return retryOrThrow(new fio.FileRequestError(responseInfo));
            }

            return ajax(opt).then(success, fail);
        }

        return tryRequest();
    }


    // 网盘 IO 提供实现
    fio.provider.register('netdisk', {
        handle: handle
    });
})(window, jQuery);
/**
 * marked - a markdown parser
 * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
 * https://github.com/chjj/marked
 */

;(function() {

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,
  code: /^( {4}[^\n]+\n*)+/,
  fences: noop,
  hr: /^( *[-*_]){3,} *(?:\n+|$)/,
  heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
  nptable: noop,
  lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
  blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
  list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
  html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
  table: noop,
  paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
  text: /^[^\n]+/
};

block.bullet = /(?:[*+-]|\d+\.)/;
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
block.item = replace(block.item, 'gm')
  (/bull/g, block.bullet)
  ();

block.list = replace(block.list)
  (/bull/g, block.bullet)
  ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
  ('def', '\\n+(?=' + block.def.source + ')')
  ();

block.blockquote = replace(block.blockquote)
  ('def', block.def)
  ();

block._tag = '(?!(?:'
  + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
  + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
  + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';

block.html = replace(block.html)
  ('comment', /<!--[\s\S]*?-->/)
  ('closed', /<(tag)[\s\S]+?<\/\1>/)
  ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
  (/tag/g, block._tag)
  ();

block.paragraph = replace(block.paragraph)
  ('hr', block.hr)
  ('heading', block.heading)
  ('lheading', block.lheading)
  ('blockquote', block.blockquote)
  ('tag', '<' + block._tag)
  ('def', block.def)
  ();

/**
 * Normal Block Grammar
 */

block.normal = merge({}, block);

/**
 * GFM Block Grammar
 */

block.gfm = merge({}, block.normal, {
  fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
  paragraph: /^/
});

block.gfm.paragraph = replace(block.paragraph)
  ('(?!', '(?!'
    + block.gfm.fences.source.replace('\\1', '\\2') + '|'
    + block.list.source.replace('\\1', '\\3') + '|')
  ();

/**
 * GFM + Tables Block Grammar
 */

block.tables = merge({}, block.gfm, {
  nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
  table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
});

/**
 * Block Lexer
 */

function Lexer(options) {
  this.tokens = [];
  this.tokens.links = {};
  this.options = options || marked.defaults;
  this.rules = block.normal;

  if (this.options.gfm) {
    if (this.options.tables) {
      this.rules = block.tables;
    } else {
      this.rules = block.gfm;
    }
  }
}

/**
 * Expose Block Rules
 */

Lexer.rules = block;

/**
 * Static Lex Method
 */

Lexer.lex = function(src, options) {
  var lexer = new Lexer(options);
  return lexer.lex(src);
};

/**
 * Preprocessing
 */

Lexer.prototype.lex = function(src) {
  src = src
    .replace(/\r\n|\r/g, '\n')
    .replace(/\t/g, '    ')
    .replace(/\u00a0/g, ' ')
    .replace(/\u2424/g, '\n');

  return this.token(src, true);
};

/**
 * Lexing
 */

Lexer.prototype.token = function(src, top, bq) {
  var src = src.replace(/^ +$/gm, '')
    , next
    , loose
    , cap
    , bull
    , b
    , item
    , space
    , i
    , l;

  while (src) {
    // newline
    if (cap = this.rules.newline.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[0].length > 1) {
        this.tokens.push({
          type: 'space'
        });
      }
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      cap = cap[0].replace(/^ {4}/gm, '');
      this.tokens.push({
        type: 'code',
        text: !this.options.pedantic
          ? cap.replace(/\n+$/, '')
          : cap
      });
      continue;
    }

    // fences (gfm)
    if (cap = this.rules.fences.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'code',
        lang: cap[2],
        text: cap[3]
      });
      continue;
    }

    // heading
    if (cap = this.rules.heading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[1].length,
        text: cap[2]
      });
      continue;
    }

    // table no leading pipe (gfm)
    if (top && (cap = this.rules.nptable.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i].split(/ *\| */);
      }

      this.tokens.push(item);

      continue;
    }

    // lheading
    if (cap = this.rules.lheading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[2] === '=' ? 1 : 2,
        text: cap[1]
      });
      continue;
    }

    // hr
    if (cap = this.rules.hr.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'hr'
      });
      continue;
    }

    // blockquote
    if (cap = this.rules.blockquote.exec(src)) {
      src = src.substring(cap[0].length);

      this.tokens.push({
        type: 'blockquote_start'
      });

      cap = cap[0].replace(/^ *> ?/gm, '');

      // Pass `top` to keep the current
      // "toplevel" state. This is exactly
      // how markdown.pl works.
      this.token(cap, top, true);

      this.tokens.push({
        type: 'blockquote_end'
      });

      continue;
    }

    // list
    if (cap = this.rules.list.exec(src)) {
      src = src.substring(cap[0].length);
      bull = cap[2];

      this.tokens.push({
        type: 'list_start',
        ordered: bull.length > 1
      });

      // Get each top-level item.
      cap = cap[0].match(this.rules.item);

      next = false;
      l = cap.length;
      i = 0;

      for (; i < l; i++) {
        item = cap[i];

        // Remove the list item's bullet
        // so it is seen as the next token.
        space = item.length;
        item = item.replace(/^ *([*+-]|\d+\.) +/, '');

        // Outdent whatever the
        // list item contains. Hacky.
        if (~item.indexOf('\n ')) {
          space -= item.length;
          item = !this.options.pedantic
            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
            : item.replace(/^ {1,4}/gm, '');
        }

        // Determine whether the next list item belongs here.
        // Backpedal if it does not belong in this list.
        if (this.options.smartLists && i !== l - 1) {
          b = block.bullet.exec(cap[i + 1])[0];
          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
            src = cap.slice(i + 1).join('\n') + src;
            i = l - 1;
          }
        }

        // Determine whether item is loose or not.
        // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
        // for discount behavior.
        loose = next || /\n\n(?!\s*$)/.test(item);
        if (i !== l - 1) {
          next = item.charAt(item.length - 1) === '\n';
          if (!loose) loose = next;
        }

        this.tokens.push({
          type: loose
            ? 'loose_item_start'
            : 'list_item_start'
        });

        // Recurse.
        this.token(item, false, bq);

        this.tokens.push({
          type: 'list_item_end'
        });
      }

      this.tokens.push({
        type: 'list_end'
      });

      continue;
    }

    // html
    if (cap = this.rules.html.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: this.options.sanitize
          ? 'paragraph'
          : 'html',
        pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
        text: cap[0]
      });
      continue;
    }

    // def
    if ((!bq && top) && (cap = this.rules.def.exec(src))) {
      src = src.substring(cap[0].length);
      this.tokens.links[cap[1].toLowerCase()] = {
        href: cap[2],
        title: cap[3]
      };
      continue;
    }

    // table (gfm)
    if (top && (cap = this.rules.table.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i]
          .replace(/^ *\| *| *\| *$/g, '')
          .split(/ *\| */);
      }

      this.tokens.push(item);

      continue;
    }

    // top-level paragraph
    if (top && (cap = this.rules.paragraph.exec(src))) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'paragraph',
        text: cap[1].charAt(cap[1].length - 1) === '\n'
          ? cap[1].slice(0, -1)
          : cap[1]
      });
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      // Top-level should never reach here.
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'text',
        text: cap[0]
      });
      continue;
    }

    if (src) {
      throw new
        Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return this.tokens;
};

/**
 * Inline-Level Grammar
 */

var inline = {
  escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  url: noop,
  tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
  link: /^!?\[(inside)\]\(href\)/,
  reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
  strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
  em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
  code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
  br: /^ {2,}\n(?!\s*$)/,
  del: noop,
  text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
};

inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;

inline.link = replace(inline.link)
  ('inside', inline._inside)
  ('href', inline._href)
  ();

inline.reflink = replace(inline.reflink)
  ('inside', inline._inside)
  ();

/**
 * Normal Inline Grammar
 */

inline.normal = merge({}, inline);

/**
 * Pedantic Inline Grammar
 */

inline.pedantic = merge({}, inline.normal, {
  strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
  em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
});

/**
 * GFM Inline Grammar
 */

inline.gfm = merge({}, inline.normal, {
  escape: replace(inline.escape)('])', '~|])')(),
  url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
  del: /^~~(?=\S)([\s\S]*?\S)~~/,
  text: replace(inline.text)
    (']|', '~]|')
    ('|', '|https?://|')
    ()
});

/**
 * GFM + Line Breaks Inline Grammar
 */

inline.breaks = merge({}, inline.gfm, {
  br: replace(inline.br)('{2,}', '*')(),
  text: replace(inline.gfm.text)('{2,}', '*')()
});

/**
 * Inline Lexer & Compiler
 */

function InlineLexer(links, options) {
  this.options = options || marked.defaults;
  this.links = links;
  this.rules = inline.normal;
  this.renderer = this.options.renderer || new Renderer;
  this.renderer.options = this.options;

  if (!this.links) {
    throw new
      Error('Tokens array requires a `links` property.');
  }

  if (this.options.gfm) {
    if (this.options.breaks) {
      this.rules = inline.breaks;
    } else {
      this.rules = inline.gfm;
    }
  } else if (this.options.pedantic) {
    this.rules = inline.pedantic;
  }
}

/**
 * Expose Inline Rules
 */

InlineLexer.rules = inline;

/**
 * Static Lexing/Compiling Method
 */

InlineLexer.output = function(src, links, options) {
  var inline = new InlineLexer(links, options);
  return inline.output(src);
};

/**
 * Lexing/Compiling
 */

InlineLexer.prototype.output = function(src) {
  var out = ''
    , link
    , text
    , href
    , cap;

  while (src) {
    // escape
    if (cap = this.rules.escape.exec(src)) {
      src = src.substring(cap[0].length);
      out += cap[1];
      continue;
    }

    // autolink
    if (cap = this.rules.autolink.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[2] === '@') {
        text = cap[1].charAt(6) === ':'
          ? this.mangle(cap[1].substring(7))
          : this.mangle(cap[1]);
        href = this.mangle('mailto:') + text;
      } else {
        text = escape(cap[1]);
        href = text;
      }
      out += this.renderer.link(href, null, text);
      continue;
    }

    // url (gfm)
    if (!this.inLink && (cap = this.rules.url.exec(src))) {
      src = src.substring(cap[0].length);
      text = escape(cap[1]);
      href = text;
      out += this.renderer.link(href, null, text);
      continue;
    }

    // tag
    if (cap = this.rules.tag.exec(src)) {
      if (!this.inLink && /^<a /i.test(cap[0])) {
        this.inLink = true;
      } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
        this.inLink = false;
      }
      src = src.substring(cap[0].length);
      out += this.options.sanitize
        ? escape(cap[0])
        : cap[0];
      continue;
    }

    // link
    if (cap = this.rules.link.exec(src)) {
      src = src.substring(cap[0].length);
      this.inLink = true;
      out += this.outputLink(cap, {
        href: cap[2],
        title: cap[3]
      });
      this.inLink = false;
      continue;
    }

    // reflink, nolink
    if ((cap = this.rules.reflink.exec(src))
        || (cap = this.rules.nolink.exec(src))) {
      src = src.substring(cap[0].length);
      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
      link = this.links[link.toLowerCase()];
      if (!link || !link.href) {
        out += cap[0].charAt(0);
        src = cap[0].substring(1) + src;
        continue;
      }
      this.inLink = true;
      out += this.outputLink(cap, link);
      this.inLink = false;
      continue;
    }

    // strong
    if (cap = this.rules.strong.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.strong(this.output(cap[2] || cap[1]));
      continue;
    }

    // em
    if (cap = this.rules.em.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.em(this.output(cap[2] || cap[1]));
      continue;
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.codespan(escape(cap[2], true));
      continue;
    }

    // br
    if (cap = this.rules.br.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.br();
      continue;
    }

    // del (gfm)
    if (cap = this.rules.del.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.del(this.output(cap[1]));
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      src = src.substring(cap[0].length);
      out += escape(this.smartypants(cap[0]));
      continue;
    }

    if (src) {
      throw new
        Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return out;
};

/**
 * Compile Link
 */

InlineLexer.prototype.outputLink = function(cap, link) {
  var href = escape(link.href)
    , title = link.title ? escape(link.title) : null;

  return cap[0].charAt(0) !== '!'
    ? this.renderer.link(href, title, this.output(cap[1]))
    : this.renderer.image(href, title, escape(cap[1]));
};

/**
 * Smartypants Transformations
 */

InlineLexer.prototype.smartypants = function(text) {
  if (!this.options.smartypants) return text;
  return text
    // em-dashes
    .replace(/--/g, '\u2014')
    // opening singles
    .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
    // closing singles & apostrophes
    .replace(/'/g, '\u2019')
    // opening doubles
    .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
    // closing doubles
    .replace(/"/g, '\u201d')
    // ellipses
    .replace(/\.{3}/g, '\u2026');
};

/**
 * Mangle Links
 */

InlineLexer.prototype.mangle = function(text) {
  var out = ''
    , l = text.length
    , i = 0
    , ch;

  for (; i < l; i++) {
    ch = text.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

/**
 * Renderer
 */

function Renderer(options) {
  this.options = options || {};
}

Renderer.prototype.code = function(code, lang, escaped) {
  if (this.options.highlight) {
    var out = this.options.highlight(code, lang);
    if (out != null && out !== code) {
      escaped = true;
      code = out;
    }
  }

  if (!lang) {
    return '<pre><code>'
      + (escaped ? code : escape(code, true))
      + '\n</code></pre>';
  }

  return '<pre><code class="'
    + this.options.langPrefix
    + escape(lang, true)
    + '">'
    + (escaped ? code : escape(code, true))
    + '\n</code></pre>\n';
};

Renderer.prototype.blockquote = function(quote) {
  return '<blockquote>\n' + quote + '</blockquote>\n';
};

Renderer.prototype.html = function(html) {
  return html;
};

Renderer.prototype.heading = function(text, level, raw) {
  return '<h'
    + level
    + ' id="'
    + this.options.headerPrefix
    + raw.toLowerCase().replace(/[^\w]+/g, '-')
    + '">'
    + text
    + '</h'
    + level
    + '>\n';
};

Renderer.prototype.hr = function() {
  return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
};

Renderer.prototype.list = function(body, ordered) {
  var type = ordered ? 'ol' : 'ul';
  return '<' + type + '>\n' + body + '</' + type + '>\n';
};

Renderer.prototype.listitem = function(text) {
  return '<li>' + text + '</li>\n';
};

Renderer.prototype.paragraph = function(text) {
  return '<p>' + text + '</p>\n';
};

Renderer.prototype.table = function(header, body) {
  return '<table>\n'
    + '<thead>\n'
    + header
    + '</thead>\n'
    + '<tbody>\n'
    + body
    + '</tbody>\n'
    + '</table>\n';
};

Renderer.prototype.tablerow = function(content) {
  return '<tr>\n' + content + '</tr>\n';
};

Renderer.prototype.tablecell = function(content, flags) {
  var type = flags.header ? 'th' : 'td';
  var tag = flags.align
    ? '<' + type + ' style="text-align:' + flags.align + '">'
    : '<' + type + '>';
  return tag + content + '</' + type + '>\n';
};

// span level renderer
Renderer.prototype.strong = function(text) {
  return '<strong>' + text + '</strong>';
};

Renderer.prototype.em = function(text) {
  return '<em>' + text + '</em>';
};

Renderer.prototype.codespan = function(text) {
  return '<code>' + text + '</code>';
};

Renderer.prototype.br = function() {
  return this.options.xhtml ? '<br/>' : '<br>';
};

Renderer.prototype.del = function(text) {
  return '<del>' + text + '</del>';
};

Renderer.prototype.link = function(href, title, text) {
  if (this.options.sanitize) {
    try {
      var prot = decodeURIComponent(unescape(href))
        .replace(/[^\w:]/g, '')
        .toLowerCase();
    } catch (e) {
      return '';
    }
    if (prot.indexOf('javascript:') === 0) {
      return '';
    }
  }
  var out = '<a href="' + href + '"';
  if (title) {
    out += ' title="' + title + '"';
  }
  out += '>' + text + '</a>';
  return out;
};

Renderer.prototype.image = function(href, title, text) {
  var out = '<img src="' + href + '" alt="' + text + '"';
  if (title) {
    out += ' title="' + title + '"';
  }
  out += this.options.xhtml ? '/>' : '>';
  return out;
};

/**
 * Parsing & Compiling
 */

function Parser(options) {
  this.tokens = [];
  this.token = null;
  this.options = options || marked.defaults;
  this.options.renderer = this.options.renderer || new Renderer;
  this.renderer = this.options.renderer;
  this.renderer.options = this.options;
}

/**
 * Static Parse Method
 */

Parser.parse = function(src, options, renderer) {
  var parser = new Parser(options, renderer);
  return parser.parse(src);
};

/**
 * Parse Loop
 */

Parser.prototype.parse = function(src) {
  this.inline = new InlineLexer(src.links, this.options, this.renderer);
  this.tokens = src.reverse();

  var out = '';
  while (this.next()) {
    out += this.tok();
  }

  return out;
};

/**
 * Next Token
 */

Parser.prototype.next = function() {
  return this.token = this.tokens.pop();
};

/**
 * Preview Next Token
 */

Parser.prototype.peek = function() {
  return this.tokens[this.tokens.length - 1] || 0;
};

/**
 * Parse Text Tokens
 */

Parser.prototype.parseText = function() {
  var body = this.token.text;

  while (this.peek().type === 'text') {
    body += '\n' + this.next().text;
  }

  return this.inline.output(body);
};

/**
 * Parse Current Token
 */

Parser.prototype.tok = function() {
  switch (this.token.type) {
    case 'space': {
      return '';
    }
    case 'hr': {
      return this.renderer.hr();
    }
    case 'heading': {
      return this.renderer.heading(
        this.inline.output(this.token.text),
        this.token.depth,
        this.token.text);
    }
    case 'code': {
      return this.renderer.code(this.token.text,
        this.token.lang,
        this.token.escaped);
    }
    case 'table': {
      var header = ''
        , body = ''
        , i
        , row
        , cell
        , flags
        , j;

      // header
      cell = '';
      for (i = 0; i < this.token.header.length; i++) {
        flags = { header: true, align: this.token.align[i] };
        cell += this.renderer.tablecell(
          this.inline.output(this.token.header[i]),
          { header: true, align: this.token.align[i] }
        );
      }
      header += this.renderer.tablerow(cell);

      for (i = 0; i < this.token.cells.length; i++) {
        row = this.token.cells[i];

        cell = '';
        for (j = 0; j < row.length; j++) {
          cell += this.renderer.tablecell(
            this.inline.output(row[j]),
            { header: false, align: this.token.align[j] }
          );
        }

        body += this.renderer.tablerow(cell);
      }
      return this.renderer.table(header, body);
    }
    case 'blockquote_start': {
      var body = '';

      while (this.next().type !== 'blockquote_end') {
        body += this.tok();
      }

      return this.renderer.blockquote(body);
    }
    case 'list_start': {
      var body = ''
        , ordered = this.token.ordered;

      while (this.next().type !== 'list_end') {
        body += this.tok();
      }

      return this.renderer.list(body, ordered);
    }
    case 'list_item_start': {
      var body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.token.type === 'text'
          ? this.parseText()
          : this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'loose_item_start': {
      var body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'html': {
      var html = !this.token.pre && !this.options.pedantic
        ? this.inline.output(this.token.text)
        : this.token.text;
      return this.renderer.html(html);
    }
    case 'paragraph': {
      return this.renderer.paragraph(this.inline.output(this.token.text));
    }
    case 'text': {
      return this.renderer.paragraph(this.parseText());
    }
  }
};

/**
 * Helpers
 */

function escape(html, encode) {
  return html
    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function unescape(html) {
  return html.replace(/&([#\w]+);/g, function(_, n) {
    n = n.toLowerCase();
    if (n === 'colon') return ':';
    if (n.charAt(0) === '#') {
      return n.charAt(1) === 'x'
        ? String.fromCharCode(parseInt(n.substring(2), 16))
        : String.fromCharCode(+n.substring(1));
    }
    return '';
  });
}

function replace(regex, opt) {
  regex = regex.source;
  opt = opt || '';
  return function self(name, val) {
    if (!name) return new RegExp(regex, opt);
    val = val.source || val;
    val = val.replace(/(^|[^\[])\^/g, '$1');
    regex = regex.replace(name, val);
    return self;
  };
}

function noop() {}
noop.exec = noop;

function merge(obj) {
  var i = 1
    , target
    , key;

  for (; i < arguments.length; i++) {
    target = arguments[i];
    for (key in target) {
      if (Object.prototype.hasOwnProperty.call(target, key)) {
        obj[key] = target[key];
      }
    }
  }

  return obj;
}


/**
 * Marked
 */

function marked(src, opt, callback) {
  if (callback || typeof opt === 'function') {
    if (!callback) {
      callback = opt;
      opt = null;
    }

    opt = merge({}, marked.defaults, opt || {});

    var highlight = opt.highlight
      , tokens
      , pending
      , i = 0;

    try {
      tokens = Lexer.lex(src, opt)
    } catch (e) {
      return callback(e);
    }

    pending = tokens.length;

    var done = function(err) {
      if (err) {
        opt.highlight = highlight;
        return callback(err);
      }

      var out;

      try {
        out = Parser.parse(tokens, opt);
      } catch (e) {
        err = e;
      }

      opt.highlight = highlight;

      return err
        ? callback(err)
        : callback(null, out);
    };

    if (!highlight || highlight.length < 3) {
      return done();
    }

    delete opt.highlight;

    if (!pending) return done();

    for (; i < tokens.length; i++) {
      (function(token) {
        if (token.type !== 'code') {
          return --pending || done();
        }
        return highlight(token.text, token.lang, function(err, code) {
          if (err) return done(err);
          if (code == null || code === token.text) {
            return --pending || done();
          }
          token.text = code;
          token.escaped = true;
          --pending || done();
        });
      })(tokens[i]);
    }

    return;
  }
  try {
    if (opt) opt = merge({}, marked.defaults, opt);
    return Parser.parse(Lexer.lex(src, opt), opt);
  } catch (e) {
    e.message += '\nPlease report this to https://github.com/chjj/marked.';
    if ((opt || marked.defaults).silent) {
      return '<p>An error occured:</p><pre>'
        + escape(e.message + '', true)
        + '</pre>';
    }
    throw e;
  }
}

/**
 * Options
 */

marked.options =
marked.setOptions = function(opt) {
  merge(marked.defaults, opt);
  return marked;
};

marked.defaults = {
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: false,
  silent: false,
  highlight: null,
  langPrefix: 'lang-',
  smartypants: false,
  headerPrefix: '',
  renderer: new Renderer,
  xhtml: false
};

/**
 * Expose
 */

marked.Parser = Parser;
marked.parser = Parser.parse;

marked.Renderer = Renderer;

marked.Lexer = Lexer;
marked.lexer = Lexer.lex;

marked.InlineLexer = InlineLexer;
marked.inlineLexer = InlineLexer.output;

marked.parse = marked;

if (typeof module !== 'undefined' && typeof exports === 'object') {
  module.exports = marked;
} else if (typeof define === 'function' && define.amd) {
  define(function() { return marked; });
} else {
  this.marked = marked;
}

}).call(function() {
  return this || (typeof window !== 'undefined' ? window : global);
}());

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

// This is CodeMirror (http://codemirror.net), a code editor
// implemented in JavaScript on top of the browser's DOM.
//
// You can find some technical background for some of the code below
// at http://marijnhaverbeke.nl/blog/#cm-internals .

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    module.exports = mod();
  else if (typeof define == "function" && define.amd) // AMD
    return define([], mod);
  else // Plain browser env
    this.CodeMirror = mod();
})(function() {
  "use strict";

  // BROWSER SNIFFING

  // Kludges for bugs and behavior differences that can't be feature
  // detected are enabled based on userAgent etc sniffing.

  var gecko = /gecko\/\d/i.test(navigator.userAgent);
  // ie_uptoN means Internet Explorer version N or lower
  var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
  var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
  var ie = ie_upto10 || ie_11up;
  var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
  var webkit = /WebKit\//.test(navigator.userAgent);
  var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
  var chrome = /Chrome\//.test(navigator.userAgent);
  var presto = /Opera\//.test(navigator.userAgent);
  var safari = /Apple Computer/.test(navigator.vendor);
  var khtml = /KHTML\//.test(navigator.userAgent);
  var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
  var phantom = /PhantomJS/.test(navigator.userAgent);

  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
  // This is woefully incomplete. Suggestions for alternative methods welcome.
  var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
  var mac = ios || /Mac/.test(navigator.platform);
  var windows = /win/i.test(navigator.platform);

  var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
  if (presto_version) presto_version = Number(presto_version[1]);
  if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
  var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
  var captureRightClick = gecko || (ie && ie_version >= 9);

  // Optimize some code when these features are not used.
  var sawReadOnlySpans = false, sawCollapsedSpans = false;

  // EDITOR CONSTRUCTOR

  // A CodeMirror instance represents an editor. This is the object
  // that user code is usually dealing with.

  function CodeMirror(place, options) {
    if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);

    this.options = options = options ? copyObj(options) : {};
    // Determine effective options based on given values and defaults.
    copyObj(defaults, options, false);
    setGuttersForLineNumbers(options);

    var doc = options.value;
    if (typeof doc == "string") doc = new Doc(doc, options.mode);
    this.doc = doc;

    var display = this.display = new Display(place, doc);
    display.wrapper.CodeMirror = this;
    updateGutters(this);
    themeChanged(this);
    if (options.lineWrapping)
      this.display.wrapper.className += " CodeMirror-wrap";
    if (options.autofocus && !mobile) focusInput(this);

    this.state = {
      keyMaps: [],  // stores maps added by addKeyMap
      overlays: [], // highlighting overlays, as added by addOverlay
      modeGen: 0,   // bumped when mode/overlay changes, used to invalidate highlighting info
      overwrite: false, focused: false,
      suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
      pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
      draggingText: false,
      highlight: new Delayed() // stores highlight worker timeout
    };

    // Override magic textarea content restore that IE sometimes does
    // on our hidden textarea on reload
    if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20);

    registerEventHandlers(this);
    ensureGlobalHandlers();

    startOperation(this);
    this.curOp.forceUpdate = true;
    attachDoc(this, doc);

    if ((options.autofocus && !mobile) || activeElt() == display.input)
      setTimeout(bind(onFocus, this), 20);
    else
      onBlur(this);

    for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
      optionHandlers[opt](this, options[opt], Init);
    maybeUpdateLineNumberWidth(this);
    for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
    endOperation(this);
  }

  // DISPLAY CONSTRUCTOR

  // The display handles the DOM integration, both for input reading
  // and content drawing. It holds references to DOM nodes and
  // display-related state.

  function Display(place, doc) {
    var d = this;

    // The semihidden textarea that is focused when the editor is
    // focused, and receives input.
    var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
    // The textarea is kept positioned near the cursor to prevent the
    // fact that it'll be scrolled into view on input from scrolling
    // our fake cursor out of view. On webkit, when wrap=off, paste is
    // very slow. So make the area wide instead.
    if (webkit) input.style.width = "1000px";
    else input.setAttribute("wrap", "off");
    // If border: 0; -- iOS fails to open keyboard (issue #1287)
    if (ios) input.style.border = "1px solid black";
    input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");

    // Wraps and hides input textarea
    d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
    // The fake scrollbar elements.
    d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
    d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
    // Covers bottom-right square when both scrollbars are present.
    d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
    // Covers bottom of gutter when coverGutterNextToScrollbar is on
    // and h scrollbar is present.
    d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
    // Will contain the actual code, positioned to cover the viewport.
    d.lineDiv = elt("div", null, "CodeMirror-code");
    // Elements are added to these to represent selection and cursors.
    d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
    d.cursorDiv = elt("div", null, "CodeMirror-cursors");
    // A visibility: hidden element used to find the size of things.
    d.measure = elt("div", null, "CodeMirror-measure");
    // When lines outside of the viewport are measured, they are drawn in this.
    d.lineMeasure = elt("div", null, "CodeMirror-measure");
    // Wraps everything that needs to exist inside the vertically-padded coordinate system
    d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
                      null, "position: relative; outline: none");
    // Moved around its parent to cover visible view.
    d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
    // Set to the height of the document, allowing scrolling.
    d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
    // Behavior of elts with overflow: auto and padding is
    // inconsistent across browsers. This is used to ensure the
    // scrollable area is big enough.
    d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
    // Will contain the gutters, if any.
    d.gutters = elt("div", null, "CodeMirror-gutters");
    d.lineGutter = null;
    // Actual scrollable element.
    d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
    d.scroller.setAttribute("tabIndex", "-1");
    // The element in which the editor lives.
    d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
                            d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");

    // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
    if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
    // Needed to hide big blue blinking cursor on Mobile Safari
    if (ios) input.style.width = "0px";
    if (!webkit) d.scroller.draggable = true;
    // Needed to handle Tab key in KHTML
    if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
    if (ie && ie_version < 8) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px";

    if (place.appendChild) place.appendChild(d.wrapper);
    else place(d.wrapper);

    // Current rendered range (may be bigger than the view window).
    d.viewFrom = d.viewTo = doc.first;
    // Information about the rendered lines.
    d.view = [];
    // Holds info about a single rendered line when it was rendered
    // for measurement, while not in view.
    d.externalMeasured = null;
    // Empty space (in pixels) above the view
    d.viewOffset = 0;
    d.lastSizeC = 0;
    d.updateLineNumbers = null;

    // Used to only resize the line number gutter when necessary (when
    // the amount of lines crosses a boundary that makes its width change)
    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
    // See readInput and resetInput
    d.prevInput = "";
    // Set to true when a non-horizontal-scrolling line widget is
    // added. As an optimization, line widget aligning is skipped when
    // this is false.
    d.alignWidgets = false;
    // Flag that indicates whether we expect input to appear real soon
    // now (after some event like 'keypress' or 'input') and are
    // polling intensively.
    d.pollingFast = false;
    // Self-resetting timeout for the poller
    d.poll = new Delayed();

    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;

    // Tracks when resetInput has punted to just putting a short
    // string into the textarea instead of the full selection.
    d.inaccurateSelection = false;

    // Tracks the maximum line length so that the horizontal scrollbar
    // can be kept static when scrolling.
    d.maxLine = null;
    d.maxLineLength = 0;
    d.maxLineChanged = false;

    // Used for measuring wheel scrolling granularity
    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;

    // True when shift is held down.
    d.shift = false;

    // Used to track whether anything happened since the context menu
    // was opened.
    d.selForContextMenu = null;
  }

  // STATE UPDATES

  // Used to get the editor into a consistent state again when options change.

  function loadMode(cm) {
    cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
    resetModeState(cm);
  }

  function resetModeState(cm) {
    cm.doc.iter(function(line) {
      if (line.stateAfter) line.stateAfter = null;
      if (line.styles) line.styles = null;
    });
    cm.doc.frontier = cm.doc.first;
    startWorker(cm, 100);
    cm.state.modeGen++;
    if (cm.curOp) regChange(cm);
  }

  function wrappingChanged(cm) {
    if (cm.options.lineWrapping) {
      addClass(cm.display.wrapper, "CodeMirror-wrap");
      cm.display.sizer.style.minWidth = "";
    } else {
      rmClass(cm.display.wrapper, "CodeMirror-wrap");
      findMaxLine(cm);
    }
    estimateLineHeights(cm);
    regChange(cm);
    clearCaches(cm);
    setTimeout(function(){updateScrollbars(cm);}, 100);
  }

  // Returns a function that estimates the height of a line, to use as
  // first approximation until the line becomes visible (and is thus
  // properly measurable).
  function estimateHeight(cm) {
    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
    return function(line) {
      if (lineIsHidden(cm.doc, line)) return 0;

      var widgetsHeight = 0;
      if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
        if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
      }

      if (wrapping)
        return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th;
      else
        return widgetsHeight + th;
    };
  }

  function estimateLineHeights(cm) {
    var doc = cm.doc, est = estimateHeight(cm);
    doc.iter(function(line) {
      var estHeight = est(line);
      if (estHeight != line.height) updateLineHeight(line, estHeight);
    });
  }

  function keyMapChanged(cm) {
    var map = keyMap[cm.options.keyMap], style = map.style;
    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
      (style ? " cm-keymap-" + style : "");
  }

  function themeChanged(cm) {
    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
      cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
    clearCaches(cm);
  }

  function guttersChanged(cm) {
    updateGutters(cm);
    regChange(cm);
    setTimeout(function(){alignHorizontally(cm);}, 20);
  }

  // Rebuild the gutter elements, ensure the margin to the left of the
  // code matches their width.
  function updateGutters(cm) {
    var gutters = cm.display.gutters, specs = cm.options.gutters;
    removeChildren(gutters);
    for (var i = 0; i < specs.length; ++i) {
      var gutterClass = specs[i];
      var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
      if (gutterClass == "CodeMirror-linenumbers") {
        cm.display.lineGutter = gElt;
        gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
      }
    }
    gutters.style.display = i ? "" : "none";
    updateGutterSpace(cm);
  }

  function updateGutterSpace(cm) {
    var width = cm.display.gutters.offsetWidth;
    cm.display.sizer.style.marginLeft = width + "px";
    cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
  }

  // Compute the character length of a line, taking into account
  // collapsed ranges (see markText) that might hide parts, and join
  // other lines onto it.
  function lineLength(line) {
    if (line.height == 0) return 0;
    var len = line.text.length, merged, cur = line;
    while (merged = collapsedSpanAtStart(cur)) {
      var found = merged.find(0, true);
      cur = found.from.line;
      len += found.from.ch - found.to.ch;
    }
    cur = line;
    while (merged = collapsedSpanAtEnd(cur)) {
      var found = merged.find(0, true);
      len -= cur.text.length - found.from.ch;
      cur = found.to.line;
      len += cur.text.length - found.to.ch;
    }
    return len;
  }

  // Find the longest line in the document.
  function findMaxLine(cm) {
    var d = cm.display, doc = cm.doc;
    d.maxLine = getLine(doc, doc.first);
    d.maxLineLength = lineLength(d.maxLine);
    d.maxLineChanged = true;
    doc.iter(function(line) {
      var len = lineLength(line);
      if (len > d.maxLineLength) {
        d.maxLineLength = len;
        d.maxLine = line;
      }
    });
  }

  // Make sure the gutters options contains the element
  // "CodeMirror-linenumbers" when the lineNumbers option is true.
  function setGuttersForLineNumbers(options) {
    var found = indexOf(options.gutters, "CodeMirror-linenumbers");
    if (found == -1 && options.lineNumbers) {
      options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
    } else if (found > -1 && !options.lineNumbers) {
      options.gutters = options.gutters.slice(0);
      options.gutters.splice(found, 1);
    }
  }

  // SCROLLBARS

  function hScrollbarTakesSpace(cm) {
    return cm.display.scroller.clientHeight - cm.display.wrapper.clientHeight < scrollerCutOff - 3;
  }

  // Prepare DOM reads needed to update the scrollbars. Done in one
  // shot to minimize update/measure roundtrips.
  function measureForScrollbars(cm) {
    var scroll = cm.display.scroller;
    return {
      clientHeight: scroll.clientHeight,
      barHeight: cm.display.scrollbarV.clientHeight,
      scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth,
      hScrollbarTakesSpace: hScrollbarTakesSpace(cm),
      barWidth: cm.display.scrollbarH.clientWidth,
      docHeight: Math.round(cm.doc.height + paddingVert(cm.display))
    };
  }

  // Re-synchronize the fake scrollbars with the actual size of the
  // content.
  function updateScrollbars(cm, measure) {
    if (!measure) measure = measureForScrollbars(cm);
    var d = cm.display, sWidth = scrollbarWidth(d.measure);
    var scrollHeight = measure.docHeight + scrollerCutOff;
    var needsH = measure.scrollWidth > measure.clientWidth;
    if (needsH && measure.scrollWidth <= measure.clientWidth + 1 &&
        sWidth > 0 && !measure.hScrollbarTakesSpace)
      needsH = false; // (Issue #2562)
    var needsV = scrollHeight > measure.clientHeight;

    if (needsV) {
      d.scrollbarV.style.display = "block";
      d.scrollbarV.style.bottom = needsH ? sWidth + "px" : "0";
      // A bug in IE8 can cause this value to be negative, so guard it.
      d.scrollbarV.firstChild.style.height =
        Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px";
    } else {
      d.scrollbarV.style.display = "";
      d.scrollbarV.firstChild.style.height = "0";
    }
    if (needsH) {
      d.scrollbarH.style.display = "block";
      d.scrollbarH.style.right = needsV ? sWidth + "px" : "0";
      d.scrollbarH.firstChild.style.width =
        (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px";
    } else {
      d.scrollbarH.style.display = "";
      d.scrollbarH.firstChild.style.width = "0";
    }
    if (needsH && needsV) {
      d.scrollbarFiller.style.display = "block";
      d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = sWidth + "px";
    } else d.scrollbarFiller.style.display = "";
    if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
      d.gutterFiller.style.display = "block";
      d.gutterFiller.style.height = sWidth + "px";
      d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
    } else d.gutterFiller.style.display = "";

    if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) {
      if (sWidth === 0) {
        var w = mac && !mac_geMountainLion ? "12px" : "18px";
        d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w;
        var barMouseDown = function(e) {
          if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
            operation(cm, onMouseDown)(e);
        };
        on(d.scrollbarV, "mousedown", barMouseDown);
        on(d.scrollbarH, "mousedown", barMouseDown);
      }
      cm.state.checkedOverlayScrollbar = true;
    }
  }

  // Compute the lines that are visible in a given viewport (defaults
  // the the current scroll position). viewport may contain top,
  // height, and ensure (see op.scrollToPos) properties.
  function visibleLines(display, doc, viewport) {
    var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;
    top = Math.floor(top - paddingTop(display));
    var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;

    var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
    // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
    // forces those lines into the viewport (if possible).
    if (viewport && viewport.ensure) {
      var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;
      if (ensureFrom < from)
        return {from: ensureFrom,
                to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)};
      if (Math.min(ensureTo, doc.lastLine()) >= to)
        return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight),
                to: ensureTo};
    }
    return {from: from, to: Math.max(to, from + 1)};
  }

  // LINE NUMBERS

  // Re-align line numbers and gutter marks to compensate for
  // horizontal scrolling.
  function alignHorizontally(cm) {
    var display = cm.display, view = display.view;
    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
    var gutterW = display.gutters.offsetWidth, left = comp + "px";
    for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
      if (cm.options.fixedGutter && view[i].gutter)
        view[i].gutter.style.left = left;
      var align = view[i].alignable;
      if (align) for (var j = 0; j < align.length; j++)
        align[j].style.left = left;
    }
    if (cm.options.fixedGutter)
      display.gutters.style.left = (comp + gutterW) + "px";
  }

  // Used to ensure that the line number gutter is still the right
  // size for the current document size. Returns true when an update
  // is needed.
  function maybeUpdateLineNumberWidth(cm) {
    if (!cm.options.lineNumbers) return false;
    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
    if (last.length != display.lineNumChars) {
      var test = display.measure.appendChild(elt("div", [elt("div", last)],
                                                 "CodeMirror-linenumber CodeMirror-gutter-elt"));
      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
      display.lineGutter.style.width = "";
      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
      display.lineNumWidth = display.lineNumInnerWidth + padding;
      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
      display.lineGutter.style.width = display.lineNumWidth + "px";
      updateGutterSpace(cm);
      return true;
    }
    return false;
  }

  function lineNumberFor(options, i) {
    return String(options.lineNumberFormatter(i + options.firstLineNumber));
  }

  // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
  // but using getBoundingClientRect to get a sub-pixel-accurate
  // result.
  function compensateForHScroll(display) {
    return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
  }

  // DISPLAY DRAWING

  function DisplayUpdate(cm, viewport, force) {
    var display = cm.display;

    this.viewport = viewport;
    // Store some values that we'll need later (but don't want to force a relayout for)
    this.visible = visibleLines(display, cm.doc, viewport);
    this.editorIsHidden = !display.wrapper.offsetWidth;
    this.wrapperHeight = display.wrapper.clientHeight;
    this.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo;
    this.oldScrollerWidth = display.scroller.clientWidth;
    this.force = force;
    this.dims = getDimensions(cm);
  }

  // Does the actual updating of the line display. Bails out
  // (returning false) when there is nothing to be done and forced is
  // false.
  function updateDisplayIfNeeded(cm, update) {
    var display = cm.display, doc = cm.doc;
    if (update.editorIsHidden) {
      resetView(cm);
      return false;
    }

    // Bail out if the visible area is already rendered and nothing changed.
    if (!update.force &&
        update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
        countDirtyView(cm) == 0)
      return false;

    if (maybeUpdateLineNumberWidth(cm)) {
      resetView(cm);
      update.dims = getDimensions(cm);
    }

    // Compute a suitable new viewport (from & to)
    var end = doc.first + doc.size;
    var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
    var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
    if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
    if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
    if (sawCollapsedSpans) {
      from = visualLineNo(cm.doc, from);
      to = visualLineEndNo(cm.doc, to);
    }

    var different = from != display.viewFrom || to != display.viewTo ||
      display.lastSizeC != update.wrapperHeight;
    adjustView(cm, from, to);

    display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
    // Position the mover div to align with the current scroll position
    cm.display.mover.style.top = display.viewOffset + "px";

    var toUpdate = countDirtyView(cm);
    if (!different && toUpdate == 0 && !update.force &&
        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
      return false;

    // For big changes, we hide the enclosing element during the
    // update, since that speeds up the operations on most browsers.
    var focused = activeElt();
    if (toUpdate > 4) display.lineDiv.style.display = "none";
    patchDisplay(cm, display.updateLineNumbers, update.dims);
    if (toUpdate > 4) display.lineDiv.style.display = "";
    // There might have been a widget with a focused element that got
    // hidden or updated, if so re-focus it.
    if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();

    // Prevent selection and cursors from interfering with the scroll
    // width.
    removeChildren(display.cursorDiv);
    removeChildren(display.selectionDiv);

    if (different) {
      display.lastSizeC = update.wrapperHeight;
      startWorker(cm, 400);
    }

    display.updateLineNumbers = null;

    return true;
  }

  function postUpdateDisplay(cm, update) {
    var force = update.force, viewport = update.viewport;
    for (var first = true;; first = false) {
      if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) {
        force = true;
      } else {
        force = false;
        // Clip forced viewport to actual scrollable area.
        if (viewport && viewport.top != null)
          viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - scrollerCutOff -
                                    cm.display.scroller.clientHeight, viewport.top)};
        // Updated line heights might result in the drawn area not
        // actually covering the viewport. Keep looping until it does.
        update.visible = visibleLines(cm.display, cm.doc, viewport);
        if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
          break;
      }
      if (!updateDisplayIfNeeded(cm, update)) break;
      updateHeightsInViewport(cm);
      var barMeasure = measureForScrollbars(cm);
      updateSelection(cm);
      setDocumentHeight(cm, barMeasure);
      updateScrollbars(cm, barMeasure);
    }

    signalLater(cm, "update", cm);
    if (cm.display.viewFrom != update.oldViewFrom || cm.display.viewTo != update.oldViewTo)
      signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
  }

  function updateDisplaySimple(cm, viewport) {
    var update = new DisplayUpdate(cm, viewport);
    if (updateDisplayIfNeeded(cm, update)) {
      updateHeightsInViewport(cm);
      postUpdateDisplay(cm, update);
      var barMeasure = measureForScrollbars(cm);
      updateSelection(cm);
      setDocumentHeight(cm, barMeasure);
      updateScrollbars(cm, barMeasure);
    }
  }

  function setDocumentHeight(cm, measure) {
    cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px";
    cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px";
  }

  function checkForWebkitWidthBug(cm, measure) {
    // Work around Webkit bug where it sometimes reserves space for a
    // non-existing phantom scrollbar in the scroller (Issue #2420)
    if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) {
      cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px";
      cm.display.gutters.style.height = measure.docHeight + "px";
    }
  }

  // Read the actual heights of the rendered lines, and update their
  // stored heights to match.
  function updateHeightsInViewport(cm) {
    var display = cm.display;
    var prevBottom = display.lineDiv.offsetTop;
    for (var i = 0; i < display.view.length; i++) {
      var cur = display.view[i], height;
      if (cur.hidden) continue;
      if (ie && ie_version < 8) {
        var bot = cur.node.offsetTop + cur.node.offsetHeight;
        height = bot - prevBottom;
        prevBottom = bot;
      } else {
        var box = cur.node.getBoundingClientRect();
        height = box.bottom - box.top;
      }
      var diff = cur.line.height - height;
      if (height < 2) height = textHeight(display);
      if (diff > .001 || diff < -.001) {
        updateLineHeight(cur.line, height);
        updateWidgetHeight(cur.line);
        if (cur.rest) for (var j = 0; j < cur.rest.length; j++)
          updateWidgetHeight(cur.rest[j]);
      }
    }
  }

  // Read and store the height of line widgets associated with the
  // given line.
  function updateWidgetHeight(line) {
    if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
      line.widgets[i].height = line.widgets[i].node.offsetHeight;
  }

  // Do a bulk-read of the DOM positions and sizes needed to draw the
  // view, so that we don't interleave reading and writing to the DOM.
  function getDimensions(cm) {
    var d = cm.display, left = {}, width = {};
    var gutterLeft = d.gutters.clientLeft;
    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
      left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft;
      width[cm.options.gutters[i]] = n.clientWidth;
    }
    return {fixedPos: compensateForHScroll(d),
            gutterTotalWidth: d.gutters.offsetWidth,
            gutterLeft: left,
            gutterWidth: width,
            wrapperWidth: d.wrapper.clientWidth};
  }

  // Sync the actual display DOM structure with display.view, removing
  // nodes for lines that are no longer in view, and creating the ones
  // that are not there yet, and updating the ones that are out of
  // date.
  function patchDisplay(cm, updateNumbersFrom, dims) {
    var display = cm.display, lineNumbers = cm.options.lineNumbers;
    var container = display.lineDiv, cur = container.firstChild;

    function rm(node) {
      var next = node.nextSibling;
      // Works around a throw-scroll bug in OS X Webkit
      if (webkit && mac && cm.display.currentWheelTarget == node)
        node.style.display = "none";
      else
        node.parentNode.removeChild(node);
      return next;
    }

    var view = display.view, lineN = display.viewFrom;
    // Loop over the elements in the view, syncing cur (the DOM nodes
    // in display.lineDiv) with the view as we go.
    for (var i = 0; i < view.length; i++) {
      var lineView = view[i];
      if (lineView.hidden) {
      } else if (!lineView.node) { // Not drawn yet
        var node = buildLineElement(cm, lineView, lineN, dims);
        container.insertBefore(node, cur);
      } else { // Already drawn
        while (cur != lineView.node) cur = rm(cur);
        var updateNumber = lineNumbers && updateNumbersFrom != null &&
          updateNumbersFrom <= lineN && lineView.lineNumber;
        if (lineView.changes) {
          if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false;
          updateLineForChanges(cm, lineView, lineN, dims);
        }
        if (updateNumber) {
          removeChildren(lineView.lineNumber);
          lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
        }
        cur = lineView.node.nextSibling;
      }
      lineN += lineView.size;
    }
    while (cur) cur = rm(cur);
  }

  // When an aspect of a line changes, a string is added to
  // lineView.changes. This updates the relevant part of the line's
  // DOM structure.
  function updateLineForChanges(cm, lineView, lineN, dims) {
    for (var j = 0; j < lineView.changes.length; j++) {
      var type = lineView.changes[j];
      if (type == "text") updateLineText(cm, lineView);
      else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
      else if (type == "class") updateLineClasses(lineView);
      else if (type == "widget") updateLineWidgets(lineView, dims);
    }
    lineView.changes = null;
  }

  // Lines with gutter elements, widgets or a background class need to
  // be wrapped, and have the extra elements added to the wrapper div
  function ensureLineWrapped(lineView) {
    if (lineView.node == lineView.text) {
      lineView.node = elt("div", null, null, "position: relative");
      if (lineView.text.parentNode)
        lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
      lineView.node.appendChild(lineView.text);
      if (ie && ie_version < 8) lineView.node.style.zIndex = 2;
    }
    return lineView.node;
  }

  function updateLineBackground(lineView) {
    var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
    if (cls) cls += " CodeMirror-linebackground";
    if (lineView.background) {
      if (cls) lineView.background.className = cls;
      else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
    } else if (cls) {
      var wrap = ensureLineWrapped(lineView);
      lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
    }
  }

  // Wrapper around buildLineContent which will reuse the structure
  // in display.externalMeasured when possible.
  function getLineContent(cm, lineView) {
    var ext = cm.display.externalMeasured;
    if (ext && ext.line == lineView.line) {
      cm.display.externalMeasured = null;
      lineView.measure = ext.measure;
      return ext.built;
    }
    return buildLineContent(cm, lineView);
  }

  // Redraw the line's text. Interacts with the background and text
  // classes because the mode may output tokens that influence these
  // classes.
  function updateLineText(cm, lineView) {
    var cls = lineView.text.className;
    var built = getLineContent(cm, lineView);
    if (lineView.text == lineView.node) lineView.node = built.pre;
    lineView.text.parentNode.replaceChild(built.pre, lineView.text);
    lineView.text = built.pre;
    if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
      lineView.bgClass = built.bgClass;
      lineView.textClass = built.textClass;
      updateLineClasses(lineView);
    } else if (cls) {
      lineView.text.className = cls;
    }
  }

  function updateLineClasses(lineView) {
    updateLineBackground(lineView);
    if (lineView.line.wrapClass)
      ensureLineWrapped(lineView).className = lineView.line.wrapClass;
    else if (lineView.node != lineView.text)
      lineView.node.className = "";
    var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
    lineView.text.className = textClass || "";
  }

  function updateLineGutter(cm, lineView, lineN, dims) {
    if (lineView.gutter) {
      lineView.node.removeChild(lineView.gutter);
      lineView.gutter = null;
    }
    var markers = lineView.line.gutterMarkers;
    if (cm.options.lineNumbers || markers) {
      var wrap = ensureLineWrapped(lineView);
      var gutterWrap = lineView.gutter =
        wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
                              (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
                          lineView.text);
      if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
        lineView.lineNumber = gutterWrap.appendChild(
          elt("div", lineNumberFor(cm.options, lineN),
              "CodeMirror-linenumber CodeMirror-gutter-elt",
              "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
              + cm.display.lineNumInnerWidth + "px"));
      if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) {
        var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
        if (found)
          gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
                                     dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
      }
    }
  }

  function updateLineWidgets(lineView, dims) {
    if (lineView.alignable) lineView.alignable = null;
    for (var node = lineView.node.firstChild, next; node; node = next) {
      var next = node.nextSibling;
      if (node.className == "CodeMirror-linewidget")
        lineView.node.removeChild(node);
    }
    insertLineWidgets(lineView, dims);
  }

  // Build a line's DOM representation from scratch
  function buildLineElement(cm, lineView, lineN, dims) {
    var built = getLineContent(cm, lineView);
    lineView.text = lineView.node = built.pre;
    if (built.bgClass) lineView.bgClass = built.bgClass;
    if (built.textClass) lineView.textClass = built.textClass;

    updateLineClasses(lineView);
    updateLineGutter(cm, lineView, lineN, dims);
    insertLineWidgets(lineView, dims);
    return lineView.node;
  }

  // A lineView may contain multiple logical lines (when merged by
  // collapsed spans). The widgets for all of them need to be drawn.
  function insertLineWidgets(lineView, dims) {
    insertLineWidgetsFor(lineView.line, lineView, dims, true);
    if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
      insertLineWidgetsFor(lineView.rest[i], lineView, dims, false);
  }

  function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
    if (!line.widgets) return;
    var wrap = ensureLineWrapped(lineView);
    for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
      var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
      if (!widget.handleMouseEvents) node.ignoreEvents = true;
      positionLineWidget(widget, node, lineView, dims);
      if (allowAbove && widget.above)
        wrap.insertBefore(node, lineView.gutter || lineView.text);
      else
        wrap.appendChild(node);
      signalLater(widget, "redraw");
    }
  }

  function positionLineWidget(widget, node, lineView, dims) {
    if (widget.noHScroll) {
      (lineView.alignable || (lineView.alignable = [])).push(node);
      var width = dims.wrapperWidth;
      node.style.left = dims.fixedPos + "px";
      if (!widget.coverGutter) {
        width -= dims.gutterTotalWidth;
        node.style.paddingLeft = dims.gutterTotalWidth + "px";
      }
      node.style.width = width + "px";
    }
    if (widget.coverGutter) {
      node.style.zIndex = 5;
      node.style.position = "relative";
      if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
    }
  }

  // POSITION OBJECT

  // A Pos instance represents a position within the text.
  var Pos = CodeMirror.Pos = function(line, ch) {
    if (!(this instanceof Pos)) return new Pos(line, ch);
    this.line = line; this.ch = ch;
  };

  // Compare two positions, return 0 if they are the same, a negative
  // number when a is less, and a positive number otherwise.
  var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };

  function copyPos(x) {return Pos(x.line, x.ch);}
  function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
  function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }

  // SELECTION / CURSOR

  // Selection objects are immutable. A new one is created every time
  // the selection changes. A selection is one or more non-overlapping
  // (and non-touching) ranges, sorted, and an integer that indicates
  // which one is the primary selection (the one that's scrolled into
  // view, that getCursor returns, etc).
  function Selection(ranges, primIndex) {
    this.ranges = ranges;
    this.primIndex = primIndex;
  }

  Selection.prototype = {
    primary: function() { return this.ranges[this.primIndex]; },
    equals: function(other) {
      if (other == this) return true;
      if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false;
      for (var i = 0; i < this.ranges.length; i++) {
        var here = this.ranges[i], there = other.ranges[i];
        if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
      }
      return true;
    },
    deepCopy: function() {
      for (var out = [], i = 0; i < this.ranges.length; i++)
        out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head));
      return new Selection(out, this.primIndex);
    },
    somethingSelected: function() {
      for (var i = 0; i < this.ranges.length; i++)
        if (!this.ranges[i].empty()) return true;
      return false;
    },
    contains: function(pos, end) {
      if (!end) end = pos;
      for (var i = 0; i < this.ranges.length; i++) {
        var range = this.ranges[i];
        if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
          return i;
      }
      return -1;
    }
  };

  function Range(anchor, head) {
    this.anchor = anchor; this.head = head;
  }

  Range.prototype = {
    from: function() { return minPos(this.anchor, this.head); },
    to: function() { return maxPos(this.anchor, this.head); },
    empty: function() {
      return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch;
    }
  };

  // Take an unsorted, potentially overlapping set of ranges, and
  // build a selection out of it. 'Consumes' ranges array (modifying
  // it).
  function normalizeSelection(ranges, primIndex) {
    var prim = ranges[primIndex];
    ranges.sort(function(a, b) { return cmp(a.from(), b.from()); });
    primIndex = indexOf(ranges, prim);
    for (var i = 1; i < ranges.length; i++) {
      var cur = ranges[i], prev = ranges[i - 1];
      if (cmp(prev.to(), cur.from()) >= 0) {
        var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
        var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
        if (i <= primIndex) --primIndex;
        ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
      }
    }
    return new Selection(ranges, primIndex);
  }

  function simpleSelection(anchor, head) {
    return new Selection([new Range(anchor, head || anchor)], 0);
  }

  // Most of the external API clips given positions to make sure they
  // actually exist within the document.
  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
  function clipPos(doc, pos) {
    if (pos.line < doc.first) return Pos(doc.first, 0);
    var last = doc.first + doc.size - 1;
    if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
    return clipToLen(pos, getLine(doc, pos.line).text.length);
  }
  function clipToLen(pos, linelen) {
    var ch = pos.ch;
    if (ch == null || ch > linelen) return Pos(pos.line, linelen);
    else if (ch < 0) return Pos(pos.line, 0);
    else return pos;
  }
  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
  function clipPosArray(doc, array) {
    for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]);
    return out;
  }

  // SELECTION UPDATES

  // The 'scroll' parameter given to many of these indicated whether
  // the new cursor position should be scrolled into view after
  // modifying the selection.

  // If shift is held or the extend flag is set, extends a range to
  // include a given position (and optionally a second position).
  // Otherwise, simply returns the range between the given positions.
  // Used for cursor motion and such.
  function extendRange(doc, range, head, other) {
    if (doc.cm && doc.cm.display.shift || doc.extend) {
      var anchor = range.anchor;
      if (other) {
        var posBefore = cmp(head, anchor) < 0;
        if (posBefore != (cmp(other, anchor) < 0)) {
          anchor = head;
          head = other;
        } else if (posBefore != (cmp(head, other) < 0)) {
          head = other;
        }
      }
      return new Range(anchor, head);
    } else {
      return new Range(other || head, head);
    }
  }

  // Extend the primary selection range, discard the rest.
  function extendSelection(doc, head, other, options) {
    setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options);
  }

  // Extend all selections (pos is an array of selections with length
  // equal the number of selections)
  function extendSelections(doc, heads, options) {
    for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
      out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
    var newSel = normalizeSelection(out, doc.sel.primIndex);
    setSelection(doc, newSel, options);
  }

  // Updates a single range in the selection.
  function replaceOneSelection(doc, i, range, options) {
    var ranges = doc.sel.ranges.slice(0);
    ranges[i] = range;
    setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options);
  }

  // Reset the selection to a single range.
  function setSimpleSelection(doc, anchor, head, options) {
    setSelection(doc, simpleSelection(anchor, head), options);
  }

  // Give beforeSelectionChange handlers a change to influence a
  // selection update.
  function filterSelectionChange(doc, sel) {
    var obj = {
      ranges: sel.ranges,
      update: function(ranges) {
        this.ranges = [];
        for (var i = 0; i < ranges.length; i++)
          this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
                                     clipPos(doc, ranges[i].head));
      }
    };
    signal(doc, "beforeSelectionChange", doc, obj);
    if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
    if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1);
    else return sel;
  }

  function setSelectionReplaceHistory(doc, sel, options) {
    var done = doc.history.done, last = lst(done);
    if (last && last.ranges) {
      done[done.length - 1] = sel;
      setSelectionNoUndo(doc, sel, options);
    } else {
      setSelection(doc, sel, options);
    }
  }

  // Set a new selection.
  function setSelection(doc, sel, options) {
    setSelectionNoUndo(doc, sel, options);
    addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
  }

  function setSelectionNoUndo(doc, sel, options) {
    if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
      sel = filterSelectionChange(doc, sel);

    var bias = options && options.bias ||
      (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
    setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));

    if (!(options && options.scroll === false) && doc.cm)
      ensureCursorVisible(doc.cm);
  }

  function setSelectionInner(doc, sel) {
    if (sel.equals(doc.sel)) return;

    doc.sel = sel;

    if (doc.cm) {
      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
      signalCursorActivity(doc.cm);
    }
    signalLater(doc, "cursorActivity", doc);
  }

  // Verify that the selection does not partially select any atomic
  // marked ranges.
  function reCheckSelection(doc) {
    setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll);
  }

  // Return a selection that does not partially select any atomic
  // ranges.
  function skipAtomicInSelection(doc, sel, bias, mayClear) {
    var out;
    for (var i = 0; i < sel.ranges.length; i++) {
      var range = sel.ranges[i];
      var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
      var newHead = skipAtomic(doc, range.head, bias, mayClear);
      if (out || newAnchor != range.anchor || newHead != range.head) {
        if (!out) out = sel.ranges.slice(0, i);
        out[i] = new Range(newAnchor, newHead);
      }
    }
    return out ? normalizeSelection(out, sel.primIndex) : sel;
  }

  // Ensure a given position is not inside an atomic range.
  function skipAtomic(doc, pos, bias, mayClear) {
    var flipped = false, curPos = pos;
    var dir = bias || 1;
    doc.cantEdit = false;
    search: for (;;) {
      var line = getLine(doc, curPos.line);
      if (line.markedSpans) {
        for (var i = 0; i < line.markedSpans.length; ++i) {
          var sp = line.markedSpans[i], m = sp.marker;
          if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
              (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
            if (mayClear) {
              signal(m, "beforeCursorEnter");
              if (m.explicitlyCleared) {
                if (!line.markedSpans) break;
                else {--i; continue;}
              }
            }
            if (!m.atomic) continue;
            var newPos = m.find(dir < 0 ? -1 : 1);
            if (cmp(newPos, curPos) == 0) {
              newPos.ch += dir;
              if (newPos.ch < 0) {
                if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
                else newPos = null;
              } else if (newPos.ch > line.text.length) {
                if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
                else newPos = null;
              }
              if (!newPos) {
                if (flipped) {
                  // Driven in a corner -- no valid cursor position found at all
                  // -- try again *with* clearing, if we didn't already
                  if (!mayClear) return skipAtomic(doc, pos, bias, true);
                  // Otherwise, turn off editing until further notice, and return the start of the doc
                  doc.cantEdit = true;
                  return Pos(doc.first, 0);
                }
                flipped = true; newPos = pos; dir = -dir;
              }
            }
            curPos = newPos;
            continue search;
          }
        }
      }
      return curPos;
    }
  }

  // SELECTION DRAWING

  // Redraw the selection and/or cursor
  function drawSelection(cm) {
    var display = cm.display, doc = cm.doc, result = {};
    var curFragment = result.cursors = document.createDocumentFragment();
    var selFragment = result.selection = document.createDocumentFragment();

    for (var i = 0; i < doc.sel.ranges.length; i++) {
      var range = doc.sel.ranges[i];
      var collapsed = range.empty();
      if (collapsed || cm.options.showCursorWhenSelecting)
        drawSelectionCursor(cm, range, curFragment);
      if (!collapsed)
        drawSelectionRange(cm, range, selFragment);
    }

    // Move the hidden textarea near the cursor to prevent scrolling artifacts
    if (cm.options.moveInputWithCursor) {
      var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
      var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
      result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
                                          headPos.top + lineOff.top - wrapOff.top));
      result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
                                           headPos.left + lineOff.left - wrapOff.left));
    }

    return result;
  }

  function showSelection(cm, drawn) {
    removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors);
    removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection);
    if (drawn.teTop != null) {
      cm.display.inputDiv.style.top = drawn.teTop + "px";
      cm.display.inputDiv.style.left = drawn.teLeft + "px";
    }
  }

  function updateSelection(cm) {
    showSelection(cm, drawSelection(cm));
  }

  // Draws a cursor for the given range
  function drawSelectionCursor(cm, range, output) {
    var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);

    var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
    cursor.style.left = pos.left + "px";
    cursor.style.top = pos.top + "px";
    cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";

    if (pos.other) {
      // Secondary cursor, shown when on a 'jump' in bi-directional text
      var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
      otherCursor.style.display = "";
      otherCursor.style.left = pos.other.left + "px";
      otherCursor.style.top = pos.other.top + "px";
      otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
    }
  }

  // Draws the given range as a highlighted selection
  function drawSelectionRange(cm, range, output) {
    var display = cm.display, doc = cm.doc;
    var fragment = document.createDocumentFragment();
    var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right;

    function add(left, top, width, bottom) {
      if (top < 0) top = 0;
      top = Math.round(top);
      bottom = Math.round(bottom);
      fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
                               "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
                               "px; height: " + (bottom - top) + "px"));
    }

    function drawForLine(line, fromArg, toArg) {
      var lineObj = getLine(doc, line);
      var lineLen = lineObj.text.length;
      var start, end;
      function coords(ch, bias) {
        return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
      }

      iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
        var leftPos = coords(from, "left"), rightPos, left, right;
        if (from == to) {
          rightPos = leftPos;
          left = right = leftPos.left;
        } else {
          rightPos = coords(to - 1, "right");
          if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
          left = leftPos.left;
          right = rightPos.right;
        }
        if (fromArg == null && from == 0) left = leftSide;
        if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
          add(left, leftPos.top, null, leftPos.bottom);
          left = leftSide;
          if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
        }
        if (toArg == null && to == lineLen) right = rightSide;
        if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
          start = leftPos;
        if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
          end = rightPos;
        if (left < leftSide + 1) left = leftSide;
        add(left, rightPos.top, right - left, rightPos.bottom);
      });
      return {start: start, end: end};
    }

    var sFrom = range.from(), sTo = range.to();
    if (sFrom.line == sTo.line) {
      drawForLine(sFrom.line, sFrom.ch, sTo.ch);
    } else {
      var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
      var singleVLine = visualLine(fromLine) == visualLine(toLine);
      var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
      var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
      if (singleVLine) {
        if (leftEnd.top < rightStart.top - 2) {
          add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
          add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
        } else {
          add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
        }
      }
      if (leftEnd.bottom < rightStart.top)
        add(leftSide, leftEnd.bottom, null, rightStart.top);
    }

    output.appendChild(fragment);
  }

  // Cursor-blinking
  function restartBlink(cm) {
    if (!cm.state.focused) return;
    var display = cm.display;
    clearInterval(display.blinker);
    var on = true;
    display.cursorDiv.style.visibility = "";
    if (cm.options.cursorBlinkRate > 0)
      display.blinker = setInterval(function() {
        display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
      }, cm.options.cursorBlinkRate);
    else if (cm.options.cursorBlinkRate < 0)
      display.cursorDiv.style.visibility = "hidden";
  }

  // HIGHLIGHT WORKER

  function startWorker(cm, time) {
    if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
      cm.state.highlight.set(time, bind(highlightWorker, cm));
  }

  function highlightWorker(cm) {
    var doc = cm.doc;
    if (doc.frontier < doc.first) doc.frontier = doc.first;
    if (doc.frontier >= cm.display.viewTo) return;
    var end = +new Date + cm.options.workTime;
    var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
    var changedLines = [];

    doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
      if (doc.frontier >= cm.display.viewFrom) { // Visible
        var oldStyles = line.styles;
        var highlighted = highlightLine(cm, line, state, true);
        line.styles = highlighted.styles;
        var oldCls = line.styleClasses, newCls = highlighted.classes;
        if (newCls) line.styleClasses = newCls;
        else if (oldCls) line.styleClasses = null;
        var ischange = !oldStyles || oldStyles.length != line.styles.length ||
          oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
        for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
        if (ischange) changedLines.push(doc.frontier);
        line.stateAfter = copyState(doc.mode, state);
      } else {
        processLine(cm, line.text, state);
        line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
      }
      ++doc.frontier;
      if (+new Date > end) {
        startWorker(cm, cm.options.workDelay);
        return true;
      }
    });
    if (changedLines.length) runInOp(cm, function() {
      for (var i = 0; i < changedLines.length; i++)
        regLineChange(cm, changedLines[i], "text");
    });
  }

  // Finds the line to start with when starting a parse. Tries to
  // find a line with a stateAfter, so that it can start with a
  // valid state. If that fails, it returns the line with the
  // smallest indentation, which tends to need the least context to
  // parse correctly.
  function findStartLine(cm, n, precise) {
    var minindent, minline, doc = cm.doc;
    var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
    for (var search = n; search > lim; --search) {
      if (search <= doc.first) return doc.first;
      var line = getLine(doc, search - 1);
      if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
      var indented = countColumn(line.text, null, cm.options.tabSize);
      if (minline == null || minindent > indented) {
        minline = search - 1;
        minindent = indented;
      }
    }
    return minline;
  }

  function getStateBefore(cm, n, precise) {
    var doc = cm.doc, display = cm.display;
    if (!doc.mode.startState) return true;
    var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
    if (!state) state = startState(doc.mode);
    else state = copyState(doc.mode, state);
    doc.iter(pos, n, function(line) {
      processLine(cm, line.text, state);
      var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
      line.stateAfter = save ? copyState(doc.mode, state) : null;
      ++pos;
    });
    if (precise) doc.frontier = pos;
    return state;
  }

  // POSITION MEASUREMENT

  function paddingTop(display) {return display.lineSpace.offsetTop;}
  function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
  function paddingH(display) {
    if (display.cachedPaddingH) return display.cachedPaddingH;
    var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
    var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
    var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
    if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
    return data;
  }

  // Ensure the lineView.wrapping.heights array is populated. This is
  // an array of bottom offsets for the lines that make up a drawn
  // line. When lineWrapping is on, there might be more than one
  // height.
  function ensureLineHeights(cm, lineView, rect) {
    var wrapping = cm.options.lineWrapping;
    var curWidth = wrapping && cm.display.scroller.clientWidth;
    if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
      var heights = lineView.measure.heights = [];
      if (wrapping) {
        lineView.measure.width = curWidth;
        var rects = lineView.text.firstChild.getClientRects();
        for (var i = 0; i < rects.length - 1; i++) {
          var cur = rects[i], next = rects[i + 1];
          if (Math.abs(cur.bottom - next.bottom) > 2)
            heights.push((cur.bottom + next.top) / 2 - rect.top);
        }
      }
      heights.push(rect.bottom - rect.top);
    }
  }

  // Find a line map (mapping character offsets to text nodes) and a
  // measurement cache for the given line number. (A line view might
  // contain multiple lines when collapsed ranges are present.)
  function mapFromLineView(lineView, line, lineN) {
    if (lineView.line == line)
      return {map: lineView.measure.map, cache: lineView.measure.cache};
    for (var i = 0; i < lineView.rest.length; i++)
      if (lineView.rest[i] == line)
        return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]};
    for (var i = 0; i < lineView.rest.length; i++)
      if (lineNo(lineView.rest[i]) > lineN)
        return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true};
  }

  // Render a line into the hidden node display.externalMeasured. Used
  // when measurement is needed for a line that's not in the viewport.
  function updateExternalMeasurement(cm, line) {
    line = visualLine(line);
    var lineN = lineNo(line);
    var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
    view.lineN = lineN;
    var built = view.built = buildLineContent(cm, view);
    view.text = built.pre;
    removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
    return view;
  }

  // Get a {top, bottom, left, right} box (in line-local coordinates)
  // for a given character.
  function measureChar(cm, line, ch, bias) {
    return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias);
  }

  // Find a line view that corresponds to the given line number.
  function findViewForLine(cm, lineN) {
    if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
      return cm.display.view[findViewIndex(cm, lineN)];
    var ext = cm.display.externalMeasured;
    if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
      return ext;
  }

  // Measurement can be split in two steps, the set-up work that
  // applies to the whole line, and the measurement of the actual
  // character. Functions like coordsChar, that need to do a lot of
  // measurements in a row, can thus ensure that the set-up work is
  // only done once.
  function prepareMeasureForLine(cm, line) {
    var lineN = lineNo(line);
    var view = findViewForLine(cm, lineN);
    if (view && !view.text)
      view = null;
    else if (view && view.changes)
      updateLineForChanges(cm, view, lineN, getDimensions(cm));
    if (!view)
      view = updateExternalMeasurement(cm, line);

    var info = mapFromLineView(view, line, lineN);
    return {
      line: line, view: view, rect: null,
      map: info.map, cache: info.cache, before: info.before,
      hasHeights: false
    };
  }

  // Given a prepared measurement object, measures the position of an
  // actual character (or fetches it from the cache).
  function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
    if (prepared.before) ch = -1;
    var key = ch + (bias || ""), found;
    if (prepared.cache.hasOwnProperty(key)) {
      found = prepared.cache[key];
    } else {
      if (!prepared.rect)
        prepared.rect = prepared.view.text.getBoundingClientRect();
      if (!prepared.hasHeights) {
        ensureLineHeights(cm, prepared.view, prepared.rect);
        prepared.hasHeights = true;
      }
      found = measureCharInner(cm, prepared, ch, bias);
      if (!found.bogus) prepared.cache[key] = found;
    }
    return {left: found.left, right: found.right,
            top: varHeight ? found.rtop : found.top,
            bottom: varHeight ? found.rbottom : found.bottom};
  }

  var nullRect = {left: 0, right: 0, top: 0, bottom: 0};

  function measureCharInner(cm, prepared, ch, bias) {
    var map = prepared.map;

    var node, start, end, collapse;
    // First, search the line map for the text node corresponding to,
    // or closest to, the target character.
    for (var i = 0; i < map.length; i += 3) {
      var mStart = map[i], mEnd = map[i + 1];
      if (ch < mStart) {
        start = 0; end = 1;
        collapse = "left";
      } else if (ch < mEnd) {
        start = ch - mStart;
        end = start + 1;
      } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
        end = mEnd - mStart;
        start = end - 1;
        if (ch >= mEnd) collapse = "right";
      }
      if (start != null) {
        node = map[i + 2];
        if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
          collapse = bias;
        if (bias == "left" && start == 0)
          while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
            node = map[(i -= 3) + 2];
            collapse = "left";
          }
        if (bias == "right" && start == mEnd - mStart)
          while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
            node = map[(i += 3) + 2];
            collapse = "right";
          }
        break;
      }
    }

    var rect;
    if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
      for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
        while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
        while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
        if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
          rect = node.parentNode.getBoundingClientRect();
        } else if (ie && cm.options.lineWrapping) {
          var rects = range(node, start, end).getClientRects();
          if (rects.length)
            rect = rects[bias == "right" ? rects.length - 1 : 0];
          else
            rect = nullRect;
        } else {
          rect = range(node, start, end).getBoundingClientRect() || nullRect;
        }
        if (rect.left || rect.right || start == 0) break;
        end = start;
        start = start - 1;
        collapse = "right";
      }
      if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect);
    } else { // If it is a widget, simply get the box for the whole widget.
      if (start > 0) collapse = bias = "right";
      var rects;
      if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
        rect = rects[bias == "right" ? rects.length - 1 : 0];
      else
        rect = node.getBoundingClientRect();
    }
    if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
      var rSpan = node.parentNode.getClientRects()[0];
      if (rSpan)
        rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom};
      else
        rect = nullRect;
    }

    var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;
    var mid = (rtop + rbot) / 2;
    var heights = prepared.view.measure.heights;
    for (var i = 0; i < heights.length - 1; i++)
      if (mid < heights[i]) break;
    var top = i ? heights[i - 1] : 0, bot = heights[i];
    var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
                  right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
                  top: top, bottom: bot};
    if (!rect.left && !rect.right) result.bogus = true;
    if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }

    return result;
  }

  // Work around problem with bounding client rects on ranges being
  // returned incorrectly when zoomed on IE10 and below.
  function maybeUpdateRectForZooming(measure, rect) {
    if (!window.screen || screen.logicalXDPI == null ||
        screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
      return rect;
    var scaleX = screen.logicalXDPI / screen.deviceXDPI;
    var scaleY = screen.logicalYDPI / screen.deviceYDPI;
    return {left: rect.left * scaleX, right: rect.right * scaleX,
            top: rect.top * scaleY, bottom: rect.bottom * scaleY};
  }

  function clearLineMeasurementCacheFor(lineView) {
    if (lineView.measure) {
      lineView.measure.cache = {};
      lineView.measure.heights = null;
      if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
        lineView.measure.caches[i] = {};
    }
  }

  function clearLineMeasurementCache(cm) {
    cm.display.externalMeasure = null;
    removeChildren(cm.display.lineMeasure);
    for (var i = 0; i < cm.display.view.length; i++)
      clearLineMeasurementCacheFor(cm.display.view[i]);
  }

  function clearCaches(cm) {
    clearLineMeasurementCache(cm);
    cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
    if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
    cm.display.lineNumChars = null;
  }

  function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
  function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }

  // Converts a {top, bottom, left, right} box from line-local
  // coordinates into another coordinate system. Context may be one of
  // "line", "div" (display.lineDiv), "local"/null (editor), or "page".
  function intoCoordSystem(cm, lineObj, rect, context) {
    if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
      var size = widgetHeight(lineObj.widgets[i]);
      rect.top += size; rect.bottom += size;
    }
    if (context == "line") return rect;
    if (!context) context = "local";
    var yOff = heightAtLine(lineObj);
    if (context == "local") yOff += paddingTop(cm.display);
    else yOff -= cm.display.viewOffset;
    if (context == "page" || context == "window") {
      var lOff = cm.display.lineSpace.getBoundingClientRect();
      yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
      var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
      rect.left += xOff; rect.right += xOff;
    }
    rect.top += yOff; rect.bottom += yOff;
    return rect;
  }

  // Coverts a box from "div" coords to another coordinate system.
  // Context may be "window", "page", "div", or "local"/null.
  function fromCoordSystem(cm, coords, context) {
    if (context == "div") return coords;
    var left = coords.left, top = coords.top;
    // First move into "page" coordinate system
    if (context == "page") {
      left -= pageScrollX();
      top -= pageScrollY();
    } else if (context == "local" || !context) {
      var localBox = cm.display.sizer.getBoundingClientRect();
      left += localBox.left;
      top += localBox.top;
    }

    var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
    return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
  }

  function charCoords(cm, pos, context, lineObj, bias) {
    if (!lineObj) lineObj = getLine(cm.doc, pos.line);
    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context);
  }

  // Returns a box for a given cursor position, which may have an
  // 'other' property containing the position of the secondary cursor
  // on a bidi boundary.
  function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
    lineObj = lineObj || getLine(cm.doc, pos.line);
    if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
    function get(ch, right) {
      var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight);
      if (right) m.left = m.right; else m.right = m.left;
      return intoCoordSystem(cm, lineObj, m, context);
    }
    function getBidi(ch, partPos) {
      var part = order[partPos], right = part.level % 2;
      if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
        part = order[--partPos];
        ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
        right = true;
      } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
        part = order[++partPos];
        ch = bidiLeft(part) - part.level % 2;
        right = false;
      }
      if (right && ch == part.to && ch > part.from) return get(ch - 1);
      return get(ch, right);
    }
    var order = getOrder(lineObj), ch = pos.ch;
    if (!order) return get(ch);
    var partPos = getBidiPartAt(order, ch);
    var val = getBidi(ch, partPos);
    if (bidiOther != null) val.other = getBidi(ch, bidiOther);
    return val;
  }

  // Used to cheaply estimate the coordinates for a position. Used for
  // intermediate scroll updates.
  function estimateCoords(cm, pos) {
    var left = 0, pos = clipPos(cm.doc, pos);
    if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch;
    var lineObj = getLine(cm.doc, pos.line);
    var top = heightAtLine(lineObj) + paddingTop(cm.display);
    return {left: left, right: left, top: top, bottom: top + lineObj.height};
  }

  // Positions returned by coordsChar contain some extra information.
  // xRel is the relative x position of the input coordinates compared
  // to the found position (so xRel > 0 means the coordinates are to
  // the right of the character position, for example). When outside
  // is true, that means the coordinates lie outside the line's
  // vertical range.
  function PosWithInfo(line, ch, outside, xRel) {
    var pos = Pos(line, ch);
    pos.xRel = xRel;
    if (outside) pos.outside = true;
    return pos;
  }

  // Compute the character position closest to the given coordinates.
  // Input must be lineSpace-local ("div" coordinate system).
  function coordsChar(cm, x, y) {
    var doc = cm.doc;
    y += cm.display.viewOffset;
    if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
    var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
    if (lineN > last)
      return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
    if (x < 0) x = 0;

    var lineObj = getLine(doc, lineN);
    for (;;) {
      var found = coordsCharInner(cm, lineObj, lineN, x, y);
      var merged = collapsedSpanAtEnd(lineObj);
      var mergedPos = merged && merged.find(0, true);
      if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
        lineN = lineNo(lineObj = mergedPos.to.line);
      else
        return found;
    }
  }

  function coordsCharInner(cm, lineObj, lineNo, x, y) {
    var innerOff = y - heightAtLine(lineObj);
    var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
    var preparedMeasure = prepareMeasureForLine(cm, lineObj);

    function getX(ch) {
      var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure);
      wrongLine = true;
      if (innerOff > sp.bottom) return sp.left - adjust;
      else if (innerOff < sp.top) return sp.left + adjust;
      else wrongLine = false;
      return sp.left;
    }

    var bidi = getOrder(lineObj), dist = lineObj.text.length;
    var from = lineLeft(lineObj), to = lineRight(lineObj);
    var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;

    if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
    // Do a binary search between these bounds.
    for (;;) {
      if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
        var ch = x < fromX || x - fromX <= toX - x ? from : to;
        var xDiff = x - (ch == from ? fromX : toX);
        while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
        var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
                              xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
        return pos;
      }
      var step = Math.ceil(dist / 2), middle = from + step;
      if (bidi) {
        middle = from;
        for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
      }
      var middleX = getX(middle);
      if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
      else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
    }
  }

  var measureText;
  // Compute the default text height.
  function textHeight(display) {
    if (display.cachedTextHeight != null) return display.cachedTextHeight;
    if (measureText == null) {
      measureText = elt("pre");
      // Measure a bunch of lines, for browsers that compute
      // fractional heights.
      for (var i = 0; i < 49; ++i) {
        measureText.appendChild(document.createTextNode("x"));
        measureText.appendChild(elt("br"));
      }
      measureText.appendChild(document.createTextNode("x"));
    }
    removeChildrenAndAdd(display.measure, measureText);
    var height = measureText.offsetHeight / 50;
    if (height > 3) display.cachedTextHeight = height;
    removeChildren(display.measure);
    return height || 1;
  }

  // Compute the default character width.
  function charWidth(display) {
    if (display.cachedCharWidth != null) return display.cachedCharWidth;
    var anchor = elt("span", "xxxxxxxxxx");
    var pre = elt("pre", [anchor]);
    removeChildrenAndAdd(display.measure, pre);
    var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
    if (width > 2) display.cachedCharWidth = width;
    return width || 10;
  }

  // OPERATIONS

  // Operations are used to wrap a series of changes to the editor
  // state in such a way that each change won't have to update the
  // cursor and display (which would be awkward, slow, and
  // error-prone). Instead, display updates are batched and then all
  // combined and executed at once.

  var operationGroup = null;

  var nextOpId = 0;
  // Start a new operation.
  function startOperation(cm) {
    cm.curOp = {
      cm: cm,
      viewChanged: false,      // Flag that indicates that lines might need to be redrawn
      startHeight: cm.doc.height, // Used to detect need to update scrollbar
      forceUpdate: false,      // Used to force a redraw
      updateInput: null,       // Whether to reset the input textarea
      typing: false,           // Whether this reset should be careful to leave existing text (for compositing)
      changeObjs: null,        // Accumulated changes, for firing change events
      cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
      cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
      selectionChanged: false, // Whether the selection needs to be redrawn
      updateMaxLine: false,    // Set when the widest line needs to be determined anew
      scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
      scrollToPos: null,       // Used to scroll to a specific position
      id: ++nextOpId           // Unique ID
    };
    if (operationGroup) {
      operationGroup.ops.push(cm.curOp);
    } else {
      cm.curOp.ownsGroup = operationGroup = {
        ops: [cm.curOp],
        delayedCallbacks: []
      };
    }
  }

  function fireCallbacksForOps(group) {
    // Calls delayed callbacks and cursorActivity handlers until no
    // new ones appear
    var callbacks = group.delayedCallbacks, i = 0;
    do {
      for (; i < callbacks.length; i++)
        callbacks[i]();
      for (var j = 0; j < group.ops.length; j++) {
        var op = group.ops[j];
        if (op.cursorActivityHandlers)
          while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
            op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm);
      }
    } while (i < callbacks.length);
  }

  // Finish an operation, updating the display and signalling delayed events
  function endOperation(cm) {
    var op = cm.curOp, group = op.ownsGroup;
    if (!group) return;

    try { fireCallbacksForOps(group); }
    finally {
      operationGroup = null;
      for (var i = 0; i < group.ops.length; i++)
        group.ops[i].cm.curOp = null;
      endOperations(group);
    }
  }

  // The DOM updates done when an operation finishes are batched so
  // that the minimum number of relayouts are required.
  function endOperations(group) {
    var ops = group.ops;
    for (var i = 0; i < ops.length; i++) // Read DOM
      endOperation_R1(ops[i]);
    for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
      endOperation_W1(ops[i]);
    for (var i = 0; i < ops.length; i++) // Read DOM
      endOperation_R2(ops[i]);
    for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
      endOperation_W2(ops[i]);
    for (var i = 0; i < ops.length; i++) // Read DOM
      endOperation_finish(ops[i]);
  }

  function endOperation_R1(op) {
    var cm = op.cm, display = cm.display;
    if (op.updateMaxLine) findMaxLine(cm);

    op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
      op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
                         op.scrollToPos.to.line >= display.viewTo) ||
      display.maxLineChanged && cm.options.lineWrapping;
    op.update = op.mustUpdate &&
      new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
  }

  function endOperation_W1(op) {
    op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
  }

  function endOperation_R2(op) {
    var cm = op.cm, display = cm.display;
    if (op.updatedDisplay) updateHeightsInViewport(cm);

    op.barMeasure = measureForScrollbars(cm);

    // If the max line changed since it was last measured, measure it,
    // and ensure the document's width matches it.
    // updateDisplay_W2 will use these properties to do the actual resizing
    if (display.maxLineChanged && !cm.options.lineWrapping) {
      op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
      op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo +
                                  scrollerCutOff - display.scroller.clientWidth);
    }

    if (op.updatedDisplay || op.selectionChanged)
      op.newSelectionNodes = drawSelection(cm);
  }

  function endOperation_W2(op) {
    var cm = op.cm;

    if (op.adjustWidthTo != null) {
      cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
      if (op.maxScrollLeft < cm.doc.scrollLeft)
        setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true);
      cm.display.maxLineChanged = false;
    }

    if (op.newSelectionNodes)
      showSelection(cm, op.newSelectionNodes);
    if (op.updatedDisplay)
      setDocumentHeight(cm, op.barMeasure);
    if (op.updatedDisplay || op.startHeight != cm.doc.height)
      updateScrollbars(cm, op.barMeasure);

    if (op.selectionChanged) restartBlink(cm);

    if (cm.state.focused && op.updateInput)
      resetInput(cm, op.typing);
  }

  function endOperation_finish(op) {
    var cm = op.cm, display = cm.display, doc = cm.doc;

    if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1)
      updateScrollbars(cm);

    if (op.updatedDisplay) postUpdateDisplay(cm, op.update);

    // Abort mouse wheel delta measurement, when scrolling explicitly
    if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
      display.wheelStartX = display.wheelStartY = null;

    // Propagate the scroll position to the actual DOM scroller
    if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) {
      var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
      display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
    }
    if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
      var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
      display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
      alignHorizontally(cm);
    }
    // If we need to scroll a specific position into view, do so.
    if (op.scrollToPos) {
      var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
                                     clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
      if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
    }

    // Fire events for markers that are hidden/unidden by editing or
    // undoing
    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
    if (hidden) for (var i = 0; i < hidden.length; ++i)
      if (!hidden[i].lines.length) signal(hidden[i], "hide");
    if (unhidden) for (var i = 0; i < unhidden.length; ++i)
      if (unhidden[i].lines.length) signal(unhidden[i], "unhide");

    if (display.wrapper.offsetHeight)
      doc.scrollTop = cm.display.scroller.scrollTop;

    // Apply workaround for two webkit bugs
    if (op.updatedDisplay && webkit) {
      if (cm.options.lineWrapping)
        checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420)
      if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth &&
          op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 &&
          !hScrollbarTakesSpace(cm))
        updateScrollbars(cm); // (Issue #2562)
    }

    // Fire change events, and delayed event handlers
    if (op.changeObjs)
      signal(cm, "changes", cm, op.changeObjs);
  }

  // Run the given function in an operation
  function runInOp(cm, f) {
    if (cm.curOp) return f();
    startOperation(cm);
    try { return f(); }
    finally { endOperation(cm); }
  }
  // Wraps a function in an operation. Returns the wrapped function.
  function operation(cm, f) {
    return function() {
      if (cm.curOp) return f.apply(cm, arguments);
      startOperation(cm);
      try { return f.apply(cm, arguments); }
      finally { endOperation(cm); }
    };
  }
  // Used to add methods to editor and doc instances, wrapping them in
  // operations.
  function methodOp(f) {
    return function() {
      if (this.curOp) return f.apply(this, arguments);
      startOperation(this);
      try { return f.apply(this, arguments); }
      finally { endOperation(this); }
    };
  }
  function docMethodOp(f) {
    return function() {
      var cm = this.cm;
      if (!cm || cm.curOp) return f.apply(this, arguments);
      startOperation(cm);
      try { return f.apply(this, arguments); }
      finally { endOperation(cm); }
    };
  }

  // VIEW TRACKING

  // These objects are used to represent the visible (currently drawn)
  // part of the document. A LineView may correspond to multiple
  // logical lines, if those are connected by collapsed ranges.
  function LineView(doc, line, lineN) {
    // The starting line
    this.line = line;
    // Continuing lines, if any
    this.rest = visualLineContinued(line);
    // Number of logical lines in this visual line
    this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
    this.node = this.text = null;
    this.hidden = lineIsHidden(doc, line);
  }

  // Create a range of LineView objects for the given lines.
  function buildViewArray(cm, from, to) {
    var array = [], nextPos;
    for (var pos = from; pos < to; pos = nextPos) {
      var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
      nextPos = pos + view.size;
      array.push(view);
    }
    return array;
  }

  // Updates the display.view data structure for a given change to the
  // document. From and to are in pre-change coordinates. Lendiff is
  // the amount of lines added or subtracted by the change. This is
  // used for changes that span multiple lines, or change the way
  // lines are divided into visual lines. regLineChange (below)
  // registers single-line changes.
  function regChange(cm, from, to, lendiff) {
    if (from == null) from = cm.doc.first;
    if (to == null) to = cm.doc.first + cm.doc.size;
    if (!lendiff) lendiff = 0;

    var display = cm.display;
    if (lendiff && to < display.viewTo &&
        (display.updateLineNumbers == null || display.updateLineNumbers > from))
      display.updateLineNumbers = from;

    cm.curOp.viewChanged = true;

    if (from >= display.viewTo) { // Change after
      if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
        resetView(cm);
    } else if (to <= display.viewFrom) { // Change before
      if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
        resetView(cm);
      } else {
        display.viewFrom += lendiff;
        display.viewTo += lendiff;
      }
    } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
      resetView(cm);
    } else if (from <= display.viewFrom) { // Top overlap
      var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
      if (cut) {
        display.view = display.view.slice(cut.index);
        display.viewFrom = cut.lineN;
        display.viewTo += lendiff;
      } else {
        resetView(cm);
      }
    } else if (to >= display.viewTo) { // Bottom overlap
      var cut = viewCuttingPoint(cm, from, from, -1);
      if (cut) {
        display.view = display.view.slice(0, cut.index);
        display.viewTo = cut.lineN;
      } else {
        resetView(cm);
      }
    } else { // Gap in the middle
      var cutTop = viewCuttingPoint(cm, from, from, -1);
      var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
      if (cutTop && cutBot) {
        display.view = display.view.slice(0, cutTop.index)
          .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
          .concat(display.view.slice(cutBot.index));
        display.viewTo += lendiff;
      } else {
        resetView(cm);
      }
    }

    var ext = display.externalMeasured;
    if (ext) {
      if (to < ext.lineN)
        ext.lineN += lendiff;
      else if (from < ext.lineN + ext.size)
        display.externalMeasured = null;
    }
  }

  // Register a change to a single line. Type must be one of "text",
  // "gutter", "class", "widget"
  function regLineChange(cm, line, type) {
    cm.curOp.viewChanged = true;
    var display = cm.display, ext = cm.display.externalMeasured;
    if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
      display.externalMeasured = null;

    if (line < display.viewFrom || line >= display.viewTo) return;
    var lineView = display.view[findViewIndex(cm, line)];
    if (lineView.node == null) return;
    var arr = lineView.changes || (lineView.changes = []);
    if (indexOf(arr, type) == -1) arr.push(type);
  }

  // Clear the view.
  function resetView(cm) {
    cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
    cm.display.view = [];
    cm.display.viewOffset = 0;
  }

  // Find the view element corresponding to a given line. Return null
  // when the line isn't visible.
  function findViewIndex(cm, n) {
    if (n >= cm.display.viewTo) return null;
    n -= cm.display.viewFrom;
    if (n < 0) return null;
    var view = cm.display.view;
    for (var i = 0; i < view.length; i++) {
      n -= view[i].size;
      if (n < 0) return i;
    }
  }

  function viewCuttingPoint(cm, oldN, newN, dir) {
    var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
    if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
      return {index: index, lineN: newN};
    for (var i = 0, n = cm.display.viewFrom; i < index; i++)
      n += view[i].size;
    if (n != oldN) {
      if (dir > 0) {
        if (index == view.length - 1) return null;
        diff = (n + view[index].size) - oldN;
        index++;
      } else {
        diff = n - oldN;
      }
      oldN += diff; newN += diff;
    }
    while (visualLineNo(cm.doc, newN) != newN) {
      if (index == (dir < 0 ? 0 : view.length - 1)) return null;
      newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
      index += dir;
    }
    return {index: index, lineN: newN};
  }

  // Force the view to cover a given range, adding empty view element
  // or clipping off existing ones as needed.
  function adjustView(cm, from, to) {
    var display = cm.display, view = display.view;
    if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
      display.view = buildViewArray(cm, from, to);
      display.viewFrom = from;
    } else {
      if (display.viewFrom > from)
        display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view);
      else if (display.viewFrom < from)
        display.view = display.view.slice(findViewIndex(cm, from));
      display.viewFrom = from;
      if (display.viewTo < to)
        display.view = display.view.concat(buildViewArray(cm, display.viewTo, to));
      else if (display.viewTo > to)
        display.view = display.view.slice(0, findViewIndex(cm, to));
    }
    display.viewTo = to;
  }

  // Count the number of lines in the view whose DOM representation is
  // out of date (or nonexistent).
  function countDirtyView(cm) {
    var view = cm.display.view, dirty = 0;
    for (var i = 0; i < view.length; i++) {
      var lineView = view[i];
      if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty;
    }
    return dirty;
  }

  // INPUT HANDLING

  // Poll for input changes, using the normal rate of polling. This
  // runs as long as the editor is focused.
  function slowPoll(cm) {
    if (cm.display.pollingFast) return;
    cm.display.poll.set(cm.options.pollInterval, function() {
      readInput(cm);
      if (cm.state.focused) slowPoll(cm);
    });
  }

  // When an event has just come in that is likely to add or change
  // something in the input textarea, we poll faster, to ensure that
  // the change appears on the screen quickly.
  function fastPoll(cm) {
    var missed = false;
    cm.display.pollingFast = true;
    function p() {
      var changed = readInput(cm);
      if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
      else {cm.display.pollingFast = false; slowPoll(cm);}
    }
    cm.display.poll.set(20, p);
  }

  // This will be set to an array of strings when copying, so that,
  // when pasting, we know what kind of selections the copied text
  // was made out of.
  var lastCopied = null;

  // Read input from the textarea, and update the document to match.
  // When something is selected, it is present in the textarea, and
  // selected (unless it is huge, in which case a placeholder is
  // used). When nothing is selected, the cursor sits after previously
  // seen text (can be empty), which is stored in prevInput (we must
  // not reset the textarea when typing, because that breaks IME).
  function readInput(cm) {
    var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
    // Since this is called a *lot*, try to bail out as cheaply as
    // possible when it is clear that nothing happened. hasSelection
    // will be the case when there is a lot of text in the textarea,
    // in which case reading its value would be expensive.
    if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput)
      return false;
    // See paste handler for more on the fakedLastChar kludge
    if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
      input.value = input.value.substring(0, input.value.length - 1);
      cm.state.fakedLastChar = false;
    }
    var text = input.value;
    // If nothing changed, bail.
    if (text == prevInput && !cm.somethingSelected()) return false;
    // Work around nonsensical selection resetting in IE9/10, and
    // inexplicable appearance of private area unicode characters on
    // some key combos in Mac (#2689).
    if (ie && ie_version >= 9 && cm.display.inputHasSelection === text ||
        mac && /[\uf700-\uf7ff]/.test(text)) {
      resetInput(cm);
      return false;
    }

    var withOp = !cm.curOp;
    if (withOp) startOperation(cm);
    cm.display.shift = false;

    if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput)
      prevInput = "\u200b";
    // Find the part of the input that is actually new
    var same = 0, l = Math.min(prevInput.length, text.length);
    while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
    var inserted = text.slice(same), textLines = splitLines(inserted);

    // When pasing N lines into N selections, insert one line per selection
    var multiPaste = null;
    if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) {
      if (lastCopied && lastCopied.join("\n") == inserted)
        multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
      else if (textLines.length == doc.sel.ranges.length)
        multiPaste = map(textLines, function(l) { return [l]; });
    }

    // Normal behavior is to insert the new text into every selection
    for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
      var range = doc.sel.ranges[i];
      var from = range.from(), to = range.to();
      // Handle deletion
      if (same < prevInput.length)
        from = Pos(from.line, from.ch - (prevInput.length - same));
      // Handle overwrite
      else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
        to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
      var updateInput = cm.curOp.updateInput;
      var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
                         origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
      makeChange(cm.doc, changeEvent);
      signalLater(cm, "inputRead", cm, changeEvent);
      // When an 'electric' character is inserted, immediately trigger a reindent
      if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
          cm.options.smartIndent && range.head.ch < 100 &&
          (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
        var mode = cm.getModeAt(range.head);
        var end = changeEnd(changeEvent);
        if (mode.electricChars) {
          for (var j = 0; j < mode.electricChars.length; j++)
            if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
              indentLine(cm, end.line, "smart");
              break;
            }
        } else if (mode.electricInput) {
          if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
            indentLine(cm, end.line, "smart");
        }
      }
    }
    ensureCursorVisible(cm);
    cm.curOp.updateInput = updateInput;
    cm.curOp.typing = true;

    // Don't leave long text in the textarea, since it makes further polling slow
    if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
    else cm.display.prevInput = text;
    if (withOp) endOperation(cm);
    cm.state.pasteIncoming = cm.state.cutIncoming = false;
    return true;
  }

  // Reset the input to correspond to the selection (or to be empty,
  // when not typing and nothing is selected)
  function resetInput(cm, typing) {
    var minimal, selected, doc = cm.doc;
    if (cm.somethingSelected()) {
      cm.display.prevInput = "";
      var range = doc.sel.primary();
      minimal = hasCopyEvent &&
        (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
      var content = minimal ? "-" : selected || cm.getSelection();
      cm.display.input.value = content;
      if (cm.state.focused) selectInput(cm.display.input);
      if (ie && ie_version >= 9) cm.display.inputHasSelection = content;
    } else if (!typing) {
      cm.display.prevInput = cm.display.input.value = "";
      if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
    }
    cm.display.inaccurateSelection = minimal;
  }

  function focusInput(cm) {
    if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input))
      cm.display.input.focus();
  }

  function ensureFocus(cm) {
    if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
  }

  function isReadOnly(cm) {
    return cm.options.readOnly || cm.doc.cantEdit;
  }

  // EVENT HANDLERS

  // Attach the necessary event handlers when initializing the editor
  function registerEventHandlers(cm) {
    var d = cm.display;
    on(d.scroller, "mousedown", operation(cm, onMouseDown));
    // Older IE's will not fire a second mousedown for a double click
    if (ie && ie_version < 11)
      on(d.scroller, "dblclick", operation(cm, function(e) {
        if (signalDOMEvent(cm, e)) return;
        var pos = posFromMouse(cm, e);
        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
        e_preventDefault(e);
        var word = cm.findWordAt(pos);
        extendSelection(cm.doc, word.anchor, word.head);
      }));
    else
      on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
    // Prevent normal selection in the editor (we handle our own)
    on(d.lineSpace, "selectstart", function(e) {
      if (!eventInWidget(d, e)) e_preventDefault(e);
    });
    // Some browsers fire contextmenu *after* opening the menu, at
    // which point we can't mess with it anymore. Context menu is
    // handled in onMouseDown for these browsers.
    if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});

    // Sync scrolling between fake scrollbars and real scrollable
    // area, ensure viewport is updated when scrolling.
    on(d.scroller, "scroll", function() {
      if (d.scroller.clientHeight) {
        setScrollTop(cm, d.scroller.scrollTop);
        setScrollLeft(cm, d.scroller.scrollLeft, true);
        signal(cm, "scroll", cm);
      }
    });
    on(d.scrollbarV, "scroll", function() {
      if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
    });
    on(d.scrollbarH, "scroll", function() {
      if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
    });

    // Listen to wheel events in order to try and update the viewport on time.
    on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
    on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});

    // Prevent clicks in the scrollbars from killing focus
    function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
    on(d.scrollbarH, "mousedown", reFocus);
    on(d.scrollbarV, "mousedown", reFocus);
    // Prevent wrapper from ever scrolling
    on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });

    on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); });
    on(d.input, "input", function() {
      if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
      fastPoll(cm);
    });
    on(d.input, "keydown", operation(cm, onKeyDown));
    on(d.input, "keypress", operation(cm, onKeyPress));
    on(d.input, "focus", bind(onFocus, cm));
    on(d.input, "blur", bind(onBlur, cm));

    function drag_(e) {
      if (!signalDOMEvent(cm, e)) e_stop(e);
    }
    if (cm.options.dragDrop) {
      on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
      on(d.scroller, "dragenter", drag_);
      on(d.scroller, "dragover", drag_);
      on(d.scroller, "drop", operation(cm, onDrop));
    }
    on(d.scroller, "paste", function(e) {
      if (eventInWidget(d, e)) return;
      cm.state.pasteIncoming = true;
      focusInput(cm);
      fastPoll(cm);
    });
    on(d.input, "paste", function() {
      // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
      // Add a char to the end of textarea before paste occur so that
      // selection doesn't span to the end of textarea.
      if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
        var start = d.input.selectionStart, end = d.input.selectionEnd;
        d.input.value += "$";
        // The selection end needs to be set before the start, otherwise there
        // can be an intermediate non-empty selection between the two, which
        // can override the middle-click paste buffer on linux and cause the
        // wrong thing to get pasted.
        d.input.selectionEnd = end;
        d.input.selectionStart = start;
        cm.state.fakedLastChar = true;
      }
      cm.state.pasteIncoming = true;
      fastPoll(cm);
    });

    function prepareCopyCut(e) {
      if (cm.somethingSelected()) {
        lastCopied = cm.getSelections();
        if (d.inaccurateSelection) {
          d.prevInput = "";
          d.inaccurateSelection = false;
          d.input.value = lastCopied.join("\n");
          selectInput(d.input);
        }
      } else {
        var text = [], ranges = [];
        for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
          var line = cm.doc.sel.ranges[i].head.line;
          var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
          ranges.push(lineRange);
          text.push(cm.getRange(lineRange.anchor, lineRange.head));
        }
        if (e.type == "cut") {
          cm.setSelections(ranges, null, sel_dontScroll);
        } else {
          d.prevInput = "";
          d.input.value = text.join("\n");
          selectInput(d.input);
        }
        lastCopied = text;
      }
      if (e.type == "cut") cm.state.cutIncoming = true;
    }
    on(d.input, "cut", prepareCopyCut);
    on(d.input, "copy", prepareCopyCut);

    // Needed to handle Tab key in KHTML
    if (khtml) on(d.sizer, "mouseup", function() {
      if (activeElt() == d.input) d.input.blur();
      focusInput(cm);
    });
  }

  // Called when the window resizes
  function onResize(cm) {
    // Might be a text scaling operation, clear size caches.
    var d = cm.display;
    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
    cm.setSize();
  }

  // MOUSE EVENTS

  // Return true when the given mouse event happened in a widget
  function eventInWidget(display, e) {
    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
      if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
    }
  }

  // Given a mouse event, find the corresponding position. If liberal
  // is false, it checks whether a gutter or scrollbar was clicked,
  // and returns null if it was. forRect is used by rectangular
  // selections, and tries to estimate a character position even for
  // coordinates beyond the right of the text.
  function posFromMouse(cm, e, liberal, forRect) {
    var display = cm.display;
    if (!liberal) {
      var target = e_target(e);
      if (target == display.scrollbarH || target == display.scrollbarV ||
          target == display.scrollbarFiller || target == display.gutterFiller) return null;
    }
    var x, y, space = display.lineSpace.getBoundingClientRect();
    // Fails unpredictably on IE[67] when mouse is dragged around quickly.
    try { x = e.clientX - space.left; y = e.clientY - space.top; }
    catch (e) { return null; }
    var coords = coordsChar(cm, x, y), line;
    if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
      var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
      coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
    }
    return coords;
  }

  // A mouse down can be a single click, double click, triple click,
  // start of selection drag, start of text drag, new cursor
  // (ctrl-click), rectangle drag (alt-drag), or xwin
  // middle-click-paste. Or it might be a click on something we should
  // not interfere with, such as a scrollbar or widget.
  function onMouseDown(e) {
    if (signalDOMEvent(this, e)) return;
    var cm = this, display = cm.display;
    display.shift = e.shiftKey;

    if (eventInWidget(display, e)) {
      if (!webkit) {
        // Briefly turn off draggability, to allow widgets to do
        // normal dragging things.
        display.scroller.draggable = false;
        setTimeout(function(){display.scroller.draggable = true;}, 100);
      }
      return;
    }
    if (clickInGutter(cm, e)) return;
    var start = posFromMouse(cm, e);
    window.focus();

    switch (e_button(e)) {
    case 1:
      if (start)
        leftButtonDown(cm, e, start);
      else if (e_target(e) == display.scroller)
        e_preventDefault(e);
      break;
    case 2:
      if (webkit) cm.state.lastMiddleDown = +new Date;
      if (start) extendSelection(cm.doc, start);
      setTimeout(bind(focusInput, cm), 20);
      e_preventDefault(e);
      break;
    case 3:
      if (captureRightClick) onContextMenu(cm, e);
      break;
    }
  }

  var lastClick, lastDoubleClick;
  function leftButtonDown(cm, e, start) {
    setTimeout(bind(ensureFocus, cm), 0);

    var now = +new Date, type;
    if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
      type = "triple";
    } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) {
      type = "double";
      lastDoubleClick = {time: now, pos: start};
    } else {
      type = "single";
      lastClick = {time: now, pos: start};
    }

    var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey;
    if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
        type == "single" && sel.contains(start) > -1 && sel.somethingSelected())
      leftButtonStartDrag(cm, e, start, modifier);
    else
      leftButtonSelect(cm, e, start, type, modifier);
  }

  // Start a text drag. When it ends, see if any dragging actually
  // happen, and treat as a click if it didn't.
  function leftButtonStartDrag(cm, e, start, modifier) {
    var display = cm.display;
    var dragEnd = operation(cm, function(e2) {
      if (webkit) display.scroller.draggable = false;
      cm.state.draggingText = false;
      off(document, "mouseup", dragEnd);
      off(display.scroller, "drop", dragEnd);
      if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
        e_preventDefault(e2);
        if (!modifier)
          extendSelection(cm.doc, start);
        focusInput(cm);
        // Work around unexplainable focus problem in IE9 (#2127)
        if (ie && ie_version == 9)
          setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
      }
    });
    // Let the drag handler handle this.
    if (webkit) display.scroller.draggable = true;
    cm.state.draggingText = dragEnd;
    // IE's approach to draggable
    if (display.scroller.dragDrop) display.scroller.dragDrop();
    on(document, "mouseup", dragEnd);
    on(display.scroller, "drop", dragEnd);
  }

  // Normal selection, as opposed to text dragging.
  function leftButtonSelect(cm, e, start, type, addNew) {
    var display = cm.display, doc = cm.doc;
    e_preventDefault(e);

    var ourRange, ourIndex, startSel = doc.sel;
    if (addNew && !e.shiftKey) {
      ourIndex = doc.sel.contains(start);
      if (ourIndex > -1)
        ourRange = doc.sel.ranges[ourIndex];
      else
        ourRange = new Range(start, start);
    } else {
      ourRange = doc.sel.primary();
    }

    if (e.altKey) {
      type = "rect";
      if (!addNew) ourRange = new Range(start, start);
      start = posFromMouse(cm, e, true, true);
      ourIndex = -1;
    } else if (type == "double") {
      var word = cm.findWordAt(start);
      if (cm.display.shift || doc.extend)
        ourRange = extendRange(doc, ourRange, word.anchor, word.head);
      else
        ourRange = word;
    } else if (type == "triple") {
      var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
      if (cm.display.shift || doc.extend)
        ourRange = extendRange(doc, ourRange, line.anchor, line.head);
      else
        ourRange = line;
    } else {
      ourRange = extendRange(doc, ourRange, start);
    }

    if (!addNew) {
      ourIndex = 0;
      setSelection(doc, new Selection([ourRange], 0), sel_mouse);
      startSel = doc.sel;
    } else if (ourIndex > -1) {
      replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
    } else {
      ourIndex = doc.sel.ranges.length;
      setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex),
                   {scroll: false, origin: "*mouse"});
    }

    var lastPos = start;
    function extendTo(pos) {
      if (cmp(lastPos, pos) == 0) return;
      lastPos = pos;

      if (type == "rect") {
        var ranges = [], tabSize = cm.options.tabSize;
        var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
        var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
        var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
        for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
             line <= end; line++) {
          var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
          if (left == right)
            ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos)));
          else if (text.length > leftPos)
            ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
        }
        if (!ranges.length) ranges.push(new Range(start, start));
        setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
                     {origin: "*mouse", scroll: false});
        cm.scrollIntoView(pos);
      } else {
        var oldRange = ourRange;
        var anchor = oldRange.anchor, head = pos;
        if (type != "single") {
          if (type == "double")
            var range = cm.findWordAt(pos);
          else
            var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
          if (cmp(range.anchor, anchor) > 0) {
            head = range.head;
            anchor = minPos(oldRange.from(), range.anchor);
          } else {
            head = range.anchor;
            anchor = maxPos(oldRange.to(), range.head);
          }
        }
        var ranges = startSel.ranges.slice(0);
        ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
        setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
      }
    }

    var editorSize = display.wrapper.getBoundingClientRect();
    // Used to ensure timeout re-tries don't fire when another extend
    // happened in the meantime (clearTimeout isn't reliable -- at
    // least on Chrome, the timeouts still happen even when cleared,
    // if the clear happens after their scheduled firing time).
    var counter = 0;

    function extend(e) {
      var curCount = ++counter;
      var cur = posFromMouse(cm, e, true, type == "rect");
      if (!cur) return;
      if (cmp(cur, lastPos) != 0) {
        ensureFocus(cm);
        extendTo(cur);
        var visible = visibleLines(display, doc);
        if (cur.line >= visible.to || cur.line < visible.from)
          setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
      } else {
        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
        if (outside) setTimeout(operation(cm, function() {
          if (counter != curCount) return;
          display.scroller.scrollTop += outside;
          extend(e);
        }), 50);
      }
    }

    function done(e) {
      counter = Infinity;
      e_preventDefault(e);
      focusInput(cm);
      off(document, "mousemove", move);
      off(document, "mouseup", up);
      doc.history.lastSelOrigin = null;
    }

    var move = operation(cm, function(e) {
      if (!e_button(e)) done(e);
      else extend(e);
    });
    var up = operation(cm, done);
    on(document, "mousemove", move);
    on(document, "mouseup", up);
  }

  // Determines whether an event happened in the gutter, and fires the
  // handlers for the corresponding event.
  function gutterEvent(cm, e, type, prevent, signalfn) {
    try { var mX = e.clientX, mY = e.clientY; }
    catch(e) { return false; }
    if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
    if (prevent) e_preventDefault(e);

    var display = cm.display;
    var lineBox = display.lineDiv.getBoundingClientRect();

    if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
    mY -= lineBox.top - display.viewOffset;

    for (var i = 0; i < cm.options.gutters.length; ++i) {
      var g = display.gutters.childNodes[i];
      if (g && g.getBoundingClientRect().right >= mX) {
        var line = lineAtHeight(cm.doc, mY);
        var gutter = cm.options.gutters[i];
        signalfn(cm, type, cm, line, gutter, e);
        return e_defaultPrevented(e);
      }
    }
  }

  function clickInGutter(cm, e) {
    return gutterEvent(cm, e, "gutterClick", true, signalLater);
  }

  // Kludge to work around strange IE behavior where it'll sometimes
  // re-fire a series of drag-related events right after the drop (#1551)
  var lastDrop = 0;

  function onDrop(e) {
    var cm = this;
    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
      return;
    e_preventDefault(e);
    if (ie) lastDrop = +new Date;
    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
    if (!pos || isReadOnly(cm)) return;
    // Might be a file drop, in which case we simply extract the text
    // and insert it.
    if (files && files.length && window.FileReader && window.File) {
      var n = files.length, text = Array(n), read = 0;
      var loadFile = function(file, i) {
        var reader = new FileReader;
        reader.onload = operation(cm, function() {
          text[i] = reader.result;
          if (++read == n) {
            pos = clipPos(cm.doc, pos);
            var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"};
            makeChange(cm.doc, change);
            setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
          }
        });
        reader.readAsText(file);
      };
      for (var i = 0; i < n; ++i) loadFile(files[i], i);
    } else { // Normal drop
      // Don't do a replace if the drop happened inside of the selected text.
      if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
        cm.state.draggingText(e);
        // Ensure the editor is re-focused
        setTimeout(bind(focusInput, cm), 20);
        return;
      }
      try {
        var text = e.dataTransfer.getData("Text");
        if (text) {
          if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey))
            var selected = cm.listSelections();
          setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
          if (selected) for (var i = 0; i < selected.length; ++i)
            replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
          cm.replaceSelection(text, "around", "paste");
          focusInput(cm);
        }
      }
      catch(e){}
    }
  }

  function onDragStart(cm, e) {
    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;

    e.dataTransfer.setData("Text", cm.getSelection());

    // Use dummy image instead of default browsers image.
    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
    if (e.dataTransfer.setDragImage && !safari) {
      var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
      img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
      if (presto) {
        img.width = img.height = 1;
        cm.display.wrapper.appendChild(img);
        // Force a relayout, or Opera won't use our image for some obscure reason
        img._top = img.offsetTop;
      }
      e.dataTransfer.setDragImage(img, 0, 0);
      if (presto) img.parentNode.removeChild(img);
    }
  }

  // SCROLL EVENTS

  // Sync the scrollable area and scrollbars, ensure the viewport
  // covers the visible area.
  function setScrollTop(cm, val) {
    if (Math.abs(cm.doc.scrollTop - val) < 2) return;
    cm.doc.scrollTop = val;
    if (!gecko) updateDisplaySimple(cm, {top: val});
    if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
    if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
    if (gecko) updateDisplaySimple(cm);
    startWorker(cm, 100);
  }
  // Sync scroller and scrollbar, ensure the gutter elements are
  // aligned.
  function setScrollLeft(cm, val, isScroller) {
    if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
    val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
    cm.doc.scrollLeft = val;
    alignHorizontally(cm);
    if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
    if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
  }

  // Since the delta values reported on mouse wheel events are
  // unstandardized between browsers and even browser versions, and
  // generally horribly unpredictable, this code starts by measuring
  // the scroll effect that the first few mouse wheel events have,
  // and, from that, detects the way it can convert deltas to pixel
  // offsets afterwards.
  //
  // The reason we want to know the amount a wheel event will scroll
  // is that it gives us a chance to update the display before the
  // actual scrolling happens, reducing flickering.

  var wheelSamples = 0, wheelPixelsPerUnit = null;
  // Fill in a browser-detected starting value on browsers where we
  // know one. These don't have to be accurate -- the result of them
  // being wrong would just be a slight flicker on the first wheel
  // scroll (if it is large enough).
  if (ie) wheelPixelsPerUnit = -.53;
  else if (gecko) wheelPixelsPerUnit = 15;
  else if (chrome) wheelPixelsPerUnit = -.7;
  else if (safari) wheelPixelsPerUnit = -1/3;

  function onScrollWheel(cm, e) {
    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
    else if (dy == null) dy = e.wheelDelta;

    var display = cm.display, scroll = display.scroller;
    // Quit if there's nothing to scroll here
    if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
          dy && scroll.scrollHeight > scroll.clientHeight)) return;

    // Webkit browsers on OS X abort momentum scrolls when the target
    // of the scroll event is removed from the scrollable element.
    // This hack (see related code in patchDisplay) makes sure the
    // element is kept around.
    if (dy && mac && webkit) {
      outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
        for (var i = 0; i < view.length; i++) {
          if (view[i].node == cur) {
            cm.display.currentWheelTarget = cur;
            break outer;
          }
        }
      }
    }

    // On some browsers, horizontal scrolling will cause redraws to
    // happen before the gutter has been realigned, causing it to
    // wriggle around in a most unseemly way. When we have an
    // estimated pixels/delta value, we just handle horizontal
    // scrolling entirely here. It'll be slightly off from native, but
    // better than glitching out.
    if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
      if (dy)
        setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
      setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
      e_preventDefault(e);
      display.wheelStartX = null; // Abort measurement, if in progress
      return;
    }

    // 'Project' the visible viewport to cover the area that is being
    // scrolled into view (if we know enough to estimate it).
    if (dy && wheelPixelsPerUnit != null) {
      var pixels = dy * wheelPixelsPerUnit;
      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
      if (pixels < 0) top = Math.max(0, top + pixels - 50);
      else bot = Math.min(cm.doc.height, bot + pixels + 50);
      updateDisplaySimple(cm, {top: top, bottom: bot});
    }

    if (wheelSamples < 20) {
      if (display.wheelStartX == null) {
        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
        display.wheelDX = dx; display.wheelDY = dy;
        setTimeout(function() {
          if (display.wheelStartX == null) return;
          var movedX = scroll.scrollLeft - display.wheelStartX;
          var movedY = scroll.scrollTop - display.wheelStartY;
          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
            (movedX && display.wheelDX && movedX / display.wheelDX);
          display.wheelStartX = display.wheelStartY = null;
          if (!sample) return;
          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
          ++wheelSamples;
        }, 200);
      } else {
        display.wheelDX += dx; display.wheelDY += dy;
      }
    }
  }

  // KEY EVENTS

  // Run a handler that was bound to a key.
  function doHandleBinding(cm, bound, dropShift) {
    if (typeof bound == "string") {
      bound = commands[bound];
      if (!bound) return false;
    }
    // Ensure previous input has been read, so that the handler sees a
    // consistent view of the document
    if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
    var prevShift = cm.display.shift, done = false;
    try {
      if (isReadOnly(cm)) cm.state.suppressEdits = true;
      if (dropShift) cm.display.shift = false;
      done = bound(cm) != Pass;
    } finally {
      cm.display.shift = prevShift;
      cm.state.suppressEdits = false;
    }
    return done;
  }

  // Collect the currently active keymaps.
  function allKeyMaps(cm) {
    var maps = cm.state.keyMaps.slice(0);
    if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
    maps.push(cm.options.keyMap);
    return maps;
  }

  var maybeTransition;
  // Handle a key from the keydown event.
  function handleKeyBinding(cm, e) {
    // Handle automatic keymap transitions
    var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
    clearTimeout(maybeTransition);
    if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
      if (getKeyMap(cm.options.keyMap) == startMap) {
        cm.options.keyMap = (next.call ? next.call(null, cm) : next);
        keyMapChanged(cm);
      }
    }, 50);

    var name = keyName(e, true), handled = false;
    if (!name) return false;
    var keymaps = allKeyMaps(cm);

    if (e.shiftKey) {
      // First try to resolve full name (including 'Shift-'). Failing
      // that, see if there is a cursor-motion command (starting with
      // 'go') bound to the keyname without 'Shift-'.
      handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
             || lookupKey(name, keymaps, function(b) {
                  if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
                    return doHandleBinding(cm, b);
                });
    } else {
      handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
    }

    if (handled) {
      e_preventDefault(e);
      restartBlink(cm);
      signalLater(cm, "keyHandled", cm, name, e);
    }
    return handled;
  }

  // Handle a key from the keypress event
  function handleCharBinding(cm, e, ch) {
    var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
                            function(b) { return doHandleBinding(cm, b, true); });
    if (handled) {
      e_preventDefault(e);
      restartBlink(cm);
      signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
    }
    return handled;
  }

  var lastStoppedKey = null;
  function onKeyDown(e) {
    var cm = this;
    ensureFocus(cm);
    if (signalDOMEvent(cm, e)) return;
    // IE does strange things with escape.
    if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false;
    var code = e.keyCode;
    cm.display.shift = code == 16 || e.shiftKey;
    var handled = handleKeyBinding(cm, e);
    if (presto) {
      lastStoppedKey = handled ? code : null;
      // Opera has no cut event... we try to at least catch the key combo
      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
        cm.replaceSelection("", null, "cut");
    }

    // Turn mouse into crosshair when Alt is held on Mac.
    if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
      showCrossHair(cm);
  }

  function showCrossHair(cm) {
    var lineDiv = cm.display.lineDiv;
    addClass(lineDiv, "CodeMirror-crosshair");

    function up(e) {
      if (e.keyCode == 18 || !e.altKey) {
        rmClass(lineDiv, "CodeMirror-crosshair");
        off(document, "keyup", up);
        off(document, "mouseover", up);
      }
    }
    on(document, "keyup", up);
    on(document, "mouseover", up);
  }

  function onKeyUp(e) {
    if (e.keyCode == 16) this.doc.sel.shift = false;
    signalDOMEvent(this, e);
  }

  function onKeyPress(e) {
    var cm = this;
    if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return;
    var keyCode = e.keyCode, charCode = e.charCode;
    if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
    if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
    if (handleCharBinding(cm, e, ch)) return;
    if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
    fastPoll(cm);
  }

  // FOCUS/BLUR EVENTS

  function onFocus(cm) {
    if (cm.options.readOnly == "nocursor") return;
    if (!cm.state.focused) {
      signal(cm, "focus", cm);
      cm.state.focused = true;
      addClass(cm.display.wrapper, "CodeMirror-focused");
      // The prevInput test prevents this from firing when a context
      // menu is closed (since the resetInput would kill the
      // select-all detection hack)
      if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
        resetInput(cm);
        if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
      }
    }
    slowPoll(cm);
    restartBlink(cm);
  }
  function onBlur(cm) {
    if (cm.state.focused) {
      signal(cm, "blur", cm);
      cm.state.focused = false;
      rmClass(cm.display.wrapper, "CodeMirror-focused");
    }
    clearInterval(cm.display.blinker);
    setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150);
  }

  // CONTEXT MENU HANDLING

  // To make the context menu work, we need to briefly unhide the
  // textarea (making it as unobtrusive as possible) to let the
  // right-click take effect on it.
  function onContextMenu(cm, e) {
    if (signalDOMEvent(cm, e, "contextmenu")) return;
    var display = cm.display;
    if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;

    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
    if (!pos || presto) return; // Opera is difficult.

    // Reset the current text selection only if the click is done outside of the selection
    // and 'resetSelectionOnContextMenu' option is true.
    var reset = cm.options.resetSelectionOnContextMenu;
    if (reset && cm.doc.sel.contains(pos) == -1)
      operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);

    var oldCSS = display.input.style.cssText;
    display.inputDiv.style.position = "absolute";
    display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
      "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
      (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
      "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
    if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
    focusInput(cm);
    if (webkit) window.scrollTo(null, oldScrollY);
    resetInput(cm);
    // Adds "Select all" to context menu in FF
    if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
    display.selForContextMenu = cm.doc.sel;
    clearTimeout(display.detectingSelectAll);

    // Select-all will be greyed out if there's nothing to select, so
    // this adds a zero-width space so that we can later check whether
    // it got selected.
    function prepareSelectAllHack() {
      if (display.input.selectionStart != null) {
        var selected = cm.somethingSelected();
        var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
        display.prevInput = selected ? "" : "\u200b";
        display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
        // Re-set this, in case some other handler touched the
        // selection in the meantime.
        display.selForContextMenu = cm.doc.sel;
      }
    }
    function rehide() {
      display.inputDiv.style.position = "relative";
      display.input.style.cssText = oldCSS;
      if (ie && ie_version < 9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
      slowPoll(cm);

      // Try to detect the user choosing select-all
      if (display.input.selectionStart != null) {
        if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
        var i = 0, poll = function() {
          if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
            operation(cm, commands.selectAll)(cm);
          else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
          else resetInput(cm);
        };
        display.detectingSelectAll = setTimeout(poll, 200);
      }
    }

    if (ie && ie_version >= 9) prepareSelectAllHack();
    if (captureRightClick) {
      e_stop(e);
      var mouseup = function() {
        off(window, "mouseup", mouseup);
        setTimeout(rehide, 20);
      };
      on(window, "mouseup", mouseup);
    } else {
      setTimeout(rehide, 50);
    }
  }

  function contextMenuInGutter(cm, e) {
    if (!hasHandler(cm, "gutterContextMenu")) return false;
    return gutterEvent(cm, e, "gutterContextMenu", false, signal);
  }

  // UPDATING

  // Compute the position of the end of a change (its 'to' property
  // refers to the pre-change end).
  var changeEnd = CodeMirror.changeEnd = function(change) {
    if (!change.text) return change.to;
    return Pos(change.from.line + change.text.length - 1,
               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
  };

  // Adjust a position to refer to the post-change position of the
  // same text, or the end of the change if the change covers it.
  function adjustForChange(pos, change) {
    if (cmp(pos, change.from) < 0) return pos;
    if (cmp(pos, change.to) <= 0) return changeEnd(change);

    var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
    if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch;
    return Pos(line, ch);
  }

  function computeSelAfterChange(doc, change) {
    var out = [];
    for (var i = 0; i < doc.sel.ranges.length; i++) {
      var range = doc.sel.ranges[i];
      out.push(new Range(adjustForChange(range.anchor, change),
                         adjustForChange(range.head, change)));
    }
    return normalizeSelection(out, doc.sel.primIndex);
  }

  function offsetPos(pos, old, nw) {
    if (pos.line == old.line)
      return Pos(nw.line, pos.ch - old.ch + nw.ch);
    else
      return Pos(nw.line + (pos.line - old.line), pos.ch);
  }

  // Used by replaceSelections to allow moving the selection to the
  // start or around the replaced test. Hint may be "start" or "around".
  function computeReplacedSel(doc, changes, hint) {
    var out = [];
    var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;
    for (var i = 0; i < changes.length; i++) {
      var change = changes[i];
      var from = offsetPos(change.from, oldPrev, newPrev);
      var to = offsetPos(changeEnd(change), oldPrev, newPrev);
      oldPrev = change.to;
      newPrev = to;
      if (hint == "around") {
        var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;
        out[i] = new Range(inv ? to : from, inv ? from : to);
      } else {
        out[i] = new Range(from, from);
      }
    }
    return new Selection(out, doc.sel.primIndex);
  }

  // Allow "beforeChange" event handlers to influence a change
  function filterChange(doc, change, update) {
    var obj = {
      canceled: false,
      from: change.from,
      to: change.to,
      text: change.text,
      origin: change.origin,
      cancel: function() { this.canceled = true; }
    };
    if (update) obj.update = function(from, to, text, origin) {
      if (from) this.from = clipPos(doc, from);
      if (to) this.to = clipPos(doc, to);
      if (text) this.text = text;
      if (origin !== undefined) this.origin = origin;
    };
    signal(doc, "beforeChange", doc, obj);
    if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);

    if (obj.canceled) return null;
    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
  }

  // Apply a change to a document, and add it to the document's
  // history, and propagating it to all linked documents.
  function makeChange(doc, change, ignoreReadOnly) {
    if (doc.cm) {
      if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly);
      if (doc.cm.state.suppressEdits) return;
    }

    if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
      change = filterChange(doc, change, true);
      if (!change) return;
    }

    // Possibly split or suppress the update based on the presence
    // of read-only spans in its range.
    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
    if (split) {
      for (var i = split.length - 1; i >= 0; --i)
        makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text});
    } else {
      makeChangeInner(doc, change);
    }
  }

  function makeChangeInner(doc, change) {
    if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return;
    var selAfter = computeSelAfterChange(doc, change);
    addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);

    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
    var rebased = [];

    linkedDocs(doc, function(doc, sharedHist) {
      if (!sharedHist && indexOf(rebased, doc.history) == -1) {
        rebaseHist(doc.history, change);
        rebased.push(doc.history);
      }
      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
    });
  }

  // Revert a change stored in a document's history.
  function makeChangeFromHistory(doc, type, allowSelectionOnly) {
    if (doc.cm && doc.cm.state.suppressEdits) return;

    var hist = doc.history, event, selAfter = doc.sel;
    var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;

    // Verify that there is a useable event (so that ctrl-z won't
    // needlessly clear selection events)
    for (var i = 0; i < source.length; i++) {
      event = source[i];
      if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
        break;
    }
    if (i == source.length) return;
    hist.lastOrigin = hist.lastSelOrigin = null;

    for (;;) {
      event = source.pop();
      if (event.ranges) {
        pushSelectionToHistory(event, dest);
        if (allowSelectionOnly && !event.equals(doc.sel)) {
          setSelection(doc, event, {clearRedo: false});
          return;
        }
        selAfter = event;
      }
      else break;
    }

    // Build up a reverse change object to add to the opposite history
    // stack (redo when undoing, and vice versa).
    var antiChanges = [];
    pushSelectionToHistory(selAfter, dest);
    dest.push({changes: antiChanges, generation: hist.generation});
    hist.generation = event.generation || ++hist.maxGeneration;

    var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");

    for (var i = event.changes.length - 1; i >= 0; --i) {
      var change = event.changes[i];
      change.origin = type;
      if (filter && !filterChange(doc, change, false)) {
        source.length = 0;
        return;
      }

      antiChanges.push(historyChangeFromChange(doc, change));

      var after = i ? computeSelAfterChange(doc, change) : lst(source);
      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
      if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)});
      var rebased = [];

      // Propagate to the linked documents
      linkedDocs(doc, function(doc, sharedHist) {
        if (!sharedHist && indexOf(rebased, doc.history) == -1) {
          rebaseHist(doc.history, change);
          rebased.push(doc.history);
        }
        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
      });
    }
  }

  // Sub-views need their line numbers shifted when text is added
  // above or below them in the parent document.
  function shiftDoc(doc, distance) {
    if (distance == 0) return;
    doc.first += distance;
    doc.sel = new Selection(map(doc.sel.ranges, function(range) {
      return new Range(Pos(range.anchor.line + distance, range.anchor.ch),
                       Pos(range.head.line + distance, range.head.ch));
    }), doc.sel.primIndex);
    if (doc.cm) {
      regChange(doc.cm, doc.first, doc.first - distance, distance);
      for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
        regLineChange(doc.cm, l, "gutter");
    }
  }

  // More lower-level change function, handling only a single document
  // (not linked ones).
  function makeChangeSingleDoc(doc, change, selAfter, spans) {
    if (doc.cm && !doc.cm.curOp)
      return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);

    if (change.to.line < doc.first) {
      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
      return;
    }
    if (change.from.line > doc.lastLine()) return;

    // Clip the change to the size of this doc
    if (change.from.line < doc.first) {
      var shift = change.text.length - 1 - (doc.first - change.from.line);
      shiftDoc(doc, shift);
      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
                text: [lst(change.text)], origin: change.origin};
    }
    var last = doc.lastLine();
    if (change.to.line > last) {
      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
                text: [change.text[0]], origin: change.origin};
    }

    change.removed = getBetween(doc, change.from, change.to);

    if (!selAfter) selAfter = computeSelAfterChange(doc, change);
    if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans);
    else updateDoc(doc, change, spans);
    setSelectionNoUndo(doc, selAfter, sel_dontScroll);
  }

  // Handle the interaction of a change to a document with the editor
  // that this document is part of.
  function makeChangeSingleDocInEditor(cm, change, spans) {
    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;

    var recomputeMaxLength = false, checkWidthStart = from.line;
    if (!cm.options.lineWrapping) {
      checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));
      doc.iter(checkWidthStart, to.line + 1, function(line) {
        if (line == display.maxLine) {
          recomputeMaxLength = true;
          return true;
        }
      });
    }

    if (doc.sel.contains(change.from, change.to) > -1)
      signalCursorActivity(cm);

    updateDoc(doc, change, spans, estimateHeight(cm));

    if (!cm.options.lineWrapping) {
      doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
        var len = lineLength(line);
        if (len > display.maxLineLength) {
          display.maxLine = line;
          display.maxLineLength = len;
          display.maxLineChanged = true;
          recomputeMaxLength = false;
        }
      });
      if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
    }

    // Adjust frontier, schedule worker
    doc.frontier = Math.min(doc.frontier, from.line);
    startWorker(cm, 400);

    var lendiff = change.text.length - (to.line - from.line) - 1;
    // Remember that these lines changed, for updating the display
    if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
      regLineChange(cm, from.line, "text");
    else
      regChange(cm, from.line, to.line + 1, lendiff);

    var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
    if (changeHandler || changesHandler) {
      var obj = {
        from: from, to: to,
        text: change.text,
        removed: change.removed,
        origin: change.origin
      };
      if (changeHandler) signalLater(cm, "change", cm, obj);
      if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj);
    }
    cm.display.selForContextMenu = null;
  }

  function replaceRange(doc, code, from, to, origin) {
    if (!to) to = from;
    if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; }
    if (typeof code == "string") code = splitLines(code);
    makeChange(doc, {from: from, to: to, text: code, origin: origin});
  }

  // SCROLLING THINGS INTO VIEW

  // If an editor sits on the top or bottom of the window, partially
  // scrolled out of view, this ensures that the cursor is visible.
  function maybeScrollWindow(cm, coords) {
    var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
    if (coords.top + box.top < 0) doScroll = true;
    else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
    if (doScroll != null && !phantom) {
      var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
                           (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " +
                           (coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
                           coords.left + "px; width: 2px;");
      cm.display.lineSpace.appendChild(scrollNode);
      scrollNode.scrollIntoView(doScroll);
      cm.display.lineSpace.removeChild(scrollNode);
    }
  }

  // Scroll a given position into view (immediately), verifying that
  // it actually became visible (as line heights are accurately
  // measured, the position of something may 'drift' during drawing).
  function scrollPosIntoView(cm, pos, end, margin) {
    if (margin == null) margin = 0;
    for (var limit = 0; limit < 5; limit++) {
      var changed = false, coords = cursorCoords(cm, pos);
      var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
      var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left),
                                         Math.min(coords.top, endCoords.top) - margin,
                                         Math.max(coords.left, endCoords.left),
                                         Math.max(coords.bottom, endCoords.bottom) + margin);
      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
      if (scrollPos.scrollTop != null) {
        setScrollTop(cm, scrollPos.scrollTop);
        if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
      }
      if (scrollPos.scrollLeft != null) {
        setScrollLeft(cm, scrollPos.scrollLeft);
        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
      }
      if (!changed) return coords;
    }
  }

  // Scroll a given set of coordinates into view (immediately).
  function scrollIntoView(cm, x1, y1, x2, y2) {
    var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
    if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
    if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
  }

  // Calculate a new scroll position needed to scroll the given
  // rectangle into view. Returns an object with scrollTop and
  // scrollLeft properties. When these are undefined, the
  // vertical/horizontal position does not need to be adjusted.
  function calculateScrollPos(cm, x1, y1, x2, y2) {
    var display = cm.display, snapMargin = textHeight(cm.display);
    if (y1 < 0) y1 = 0;
    var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
    var screen = display.scroller.clientHeight - scrollerCutOff, result = {};
    if (y2 - y1 > screen) y2 = y1 + screen;
    var docBottom = cm.doc.height + paddingVert(display);
    var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
    if (y1 < screentop) {
      result.scrollTop = atTop ? 0 : y1;
    } else if (y2 > screentop + screen) {
      var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
      if (newTop != screentop) result.scrollTop = newTop;
    }

    var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
    var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth;
    var tooWide = x2 - x1 > screenw;
    if (tooWide) x2 = x1 + screenw;
    if (x1 < 10)
      result.scrollLeft = 0;
    else if (x1 < screenleft)
      result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
    else if (x2 > screenw + screenleft - 3)
      result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw;

    return result;
  }

  // Store a relative adjustment to the scroll position in the current
  // operation (to be applied when the operation finishes).
  function addToScrollPos(cm, left, top) {
    if (left != null || top != null) resolveScrollToPos(cm);
    if (left != null)
      cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left;
    if (top != null)
      cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;
  }

  // Make sure that at the end of the operation the current cursor is
  // shown.
  function ensureCursorVisible(cm) {
    resolveScrollToPos(cm);
    var cur = cm.getCursor(), from = cur, to = cur;
    if (!cm.options.lineWrapping) {
      from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur;
      to = Pos(cur.line, cur.ch + 1);
    }
    cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true};
  }

  // When an operation has its scrollToPos property set, and another
  // scroll action is applied before the end of the operation, this
  // 'simulates' scrolling that position into view in a cheap way, so
  // that the effect of intermediate scroll commands is not ignored.
  function resolveScrollToPos(cm) {
    var range = cm.curOp.scrollToPos;
    if (range) {
      cm.curOp.scrollToPos = null;
      var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to);
      var sPos = calculateScrollPos(cm, Math.min(from.left, to.left),
                                    Math.min(from.top, to.top) - range.margin,
                                    Math.max(from.right, to.right),
                                    Math.max(from.bottom, to.bottom) + range.margin);
      cm.scrollTo(sPos.scrollLeft, sPos.scrollTop);
    }
  }

  // API UTILITIES

  // Indent the given line. The how parameter can be "smart",
  // "add"/null, "subtract", or "prev". When aggressive is false
  // (typically set to true for forced single-line indents), empty
  // lines are not indented, and places where the mode returns Pass
  // are left alone.
  function indentLine(cm, n, how, aggressive) {
    var doc = cm.doc, state;
    if (how == null) how = "add";
    if (how == "smart") {
      // Fall back to "prev" when the mode doesn't have an indentation
      // method.
      if (!doc.mode.indent) how = "prev";
      else state = getStateBefore(cm, n);
    }

    var tabSize = cm.options.tabSize;
    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
    if (line.stateAfter) line.stateAfter = null;
    var curSpaceString = line.text.match(/^\s*/)[0], indentation;
    if (!aggressive && !/\S/.test(line.text)) {
      indentation = 0;
      how = "not";
    } else if (how == "smart") {
      indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
      if (indentation == Pass || indentation > 150) {
        if (!aggressive) return;
        how = "prev";
      }
    }
    if (how == "prev") {
      if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
      else indentation = 0;
    } else if (how == "add") {
      indentation = curSpace + cm.options.indentUnit;
    } else if (how == "subtract") {
      indentation = curSpace - cm.options.indentUnit;
    } else if (typeof how == "number") {
      indentation = curSpace + how;
    }
    indentation = Math.max(0, indentation);

    var indentString = "", pos = 0;
    if (cm.options.indentWithTabs)
      for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
    if (pos < indentation) indentString += spaceStr(indentation - pos);

    if (indentString != curSpaceString) {
      replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
    } else {
      // Ensure that, if the cursor was in the whitespace at the start
      // of the line, it is moved to the end of that space.
      for (var i = 0; i < doc.sel.ranges.length; i++) {
        var range = doc.sel.ranges[i];
        if (range.head.line == n && range.head.ch < curSpaceString.length) {
          var pos = Pos(n, curSpaceString.length);
          replaceOneSelection(doc, i, new Range(pos, pos));
          break;
        }
      }
    }
    line.stateAfter = null;
  }

  // Utility for applying a change to a line by handle or number,
  // returning the number and optionally registering the line as
  // changed.
  function changeLine(doc, handle, changeType, op) {
    var no = handle, line = handle;
    if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
    else no = lineNo(handle);
    if (no == null) return null;
    if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType);
    return line;
  }

  // Helper for deleting text near the selection(s), used to implement
  // backspace, delete, and similar functionality.
  function deleteNearSelection(cm, compute) {
    var ranges = cm.doc.sel.ranges, kill = [];
    // Build up a set of ranges to kill first, merging overlapping
    // ranges.
    for (var i = 0; i < ranges.length; i++) {
      var toKill = compute(ranges[i]);
      while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
        var replaced = kill.pop();
        if (cmp(replaced.from, toKill.from) < 0) {
          toKill.from = replaced.from;
          break;
        }
      }
      kill.push(toKill);
    }
    // Next, remove those actual ranges.
    runInOp(cm, function() {
      for (var i = kill.length - 1; i >= 0; i--)
        replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete");
      ensureCursorVisible(cm);
    });
  }

  // Used for horizontal relative motion. Dir is -1 or 1 (left or
  // right), unit can be "char", "column" (like char, but doesn't
  // cross line boundaries), "word" (across next word), or "group" (to
  // the start of next group of word or non-word-non-whitespace
  // chars). The visually param controls whether, in right-to-left
  // text, direction 1 means to move towards the next index in the
  // string, or towards the character to the right of the current
  // position. The resulting position will have a hitSide=true
  // property if it reached the end of the document.
  function findPosH(doc, pos, dir, unit, visually) {
    var line = pos.line, ch = pos.ch, origDir = dir;
    var lineObj = getLine(doc, line);
    var possible = true;
    function findNextLine() {
      var l = line + dir;
      if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
      line = l;
      return lineObj = getLine(doc, l);
    }
    function moveOnce(boundToLine) {
      var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
      if (next == null) {
        if (!boundToLine && findNextLine()) {
          if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
          else ch = dir < 0 ? lineObj.text.length : 0;
        } else return (possible = false);
      } else ch = next;
      return true;
    }

    if (unit == "char") moveOnce();
    else if (unit == "column") moveOnce(true);
    else if (unit == "word" || unit == "group") {
      var sawType = null, group = unit == "group";
      var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
      for (var first = true;; first = false) {
        if (dir < 0 && !moveOnce(!first)) break;
        var cur = lineObj.text.charAt(ch) || "\n";
        var type = isWordChar(cur, helper) ? "w"
          : group && cur == "\n" ? "n"
          : !group || /\s/.test(cur) ? null
          : "p";
        if (group && !first && !type) type = "s";
        if (sawType && sawType != type) {
          if (dir < 0) {dir = 1; moveOnce();}
          break;
        }

        if (type) sawType = type;
        if (dir > 0 && !moveOnce(!first)) break;
      }
    }
    var result = skipAtomic(doc, Pos(line, ch), origDir, true);
    if (!possible) result.hitSide = true;
    return result;
  }

  // For relative vertical movement. Dir may be -1 or 1. Unit can be
  // "page" or "line". The resulting position will have a hitSide=true
  // property if it reached the end of the document.
  function findPosV(cm, pos, dir, unit) {
    var doc = cm.doc, x = pos.left, y;
    if (unit == "page") {
      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
      y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
    } else if (unit == "line") {
      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
    }
    for (;;) {
      var target = coordsChar(cm, x, y);
      if (!target.outside) break;
      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
      y += dir * 5;
    }
    return target;
  }

  // EDITOR METHODS

  // The publicly visible API. Note that methodOp(f) means
  // 'wrap f in an operation, performed on its `this` parameter'.

  // This is not the complete set of editor methods. Most of the
  // methods defined on the Doc type are also injected into
  // CodeMirror.prototype, for backwards compatibility and
  // convenience.

  CodeMirror.prototype = {
    constructor: CodeMirror,
    focus: function(){window.focus(); focusInput(this); fastPoll(this);},

    setOption: function(option, value) {
      var options = this.options, old = options[option];
      if (options[option] == value && option != "mode") return;
      options[option] = value;
      if (optionHandlers.hasOwnProperty(option))
        operation(this, optionHandlers[option])(this, value, old);
    },

    getOption: function(option) {return this.options[option];},
    getDoc: function() {return this.doc;},

    addKeyMap: function(map, bottom) {
      this.state.keyMaps[bottom ? "push" : "unshift"](map);
    },
    removeKeyMap: function(map) {
      var maps = this.state.keyMaps;
      for (var i = 0; i < maps.length; ++i)
        if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
          maps.splice(i, 1);
          return true;
        }
    },

    addOverlay: methodOp(function(spec, options) {
      var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
      if (mode.startState) throw new Error("Overlays may not be stateful.");
      this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
      this.state.modeGen++;
      regChange(this);
    }),
    removeOverlay: methodOp(function(spec) {
      var overlays = this.state.overlays;
      for (var i = 0; i < overlays.length; ++i) {
        var cur = overlays[i].modeSpec;
        if (cur == spec || typeof spec == "string" && cur.name == spec) {
          overlays.splice(i, 1);
          this.state.modeGen++;
          regChange(this);
          return;
        }
      }
    }),

    indentLine: methodOp(function(n, dir, aggressive) {
      if (typeof dir != "string" && typeof dir != "number") {
        if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
        else dir = dir ? "add" : "subtract";
      }
      if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
    }),
    indentSelection: methodOp(function(how) {
      var ranges = this.doc.sel.ranges, end = -1;
      for (var i = 0; i < ranges.length; i++) {
        var range = ranges[i];
        if (!range.empty()) {
          var from = range.from(), to = range.to();
          var start = Math.max(end, from.line);
          end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
          for (var j = start; j < end; ++j)
            indentLine(this, j, how);
          var newRanges = this.doc.sel.ranges;
          if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
            replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll);
        } else if (range.head.line > end) {
          indentLine(this, range.head.line, how, true);
          end = range.head.line;
          if (i == this.doc.sel.primIndex) ensureCursorVisible(this);
        }
      }
    }),

    // Fetch the parser token for a given character. Useful for hacks
    // that want to inspect the mode state (say, for completion).
    getTokenAt: function(pos, precise) {
      var doc = this.doc;
      pos = clipPos(doc, pos);
      var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
      var line = getLine(doc, pos.line);
      var stream = new StringStream(line.text, this.options.tabSize);
      while (stream.pos < pos.ch && !stream.eol()) {
        stream.start = stream.pos;
        var style = readToken(mode, stream, state);
      }
      return {start: stream.start,
              end: stream.pos,
              string: stream.current(),
              type: style || null,
              state: state};
    },

    getTokenTypeAt: function(pos) {
      pos = clipPos(this.doc, pos);
      var styles = getLineStyles(this, getLine(this.doc, pos.line));
      var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
      var type;
      if (ch == 0) type = styles[2];
      else for (;;) {
        var mid = (before + after) >> 1;
        if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
        else if (styles[mid * 2 + 1] < ch) before = mid + 1;
        else { type = styles[mid * 2 + 2]; break; }
      }
      var cut = type ? type.indexOf("cm-overlay ") : -1;
      return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
    },

    getModeAt: function(pos) {
      var mode = this.doc.mode;
      if (!mode.innerMode) return mode;
      return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
    },

    getHelper: function(pos, type) {
      return this.getHelpers(pos, type)[0];
    },

    getHelpers: function(pos, type) {
      var found = [];
      if (!helpers.hasOwnProperty(type)) return helpers;
      var help = helpers[type], mode = this.getModeAt(pos);
      if (typeof mode[type] == "string") {
        if (help[mode[type]]) found.push(help[mode[type]]);
      } else if (mode[type]) {
        for (var i = 0; i < mode[type].length; i++) {
          var val = help[mode[type][i]];
          if (val) found.push(val);
        }
      } else if (mode.helperType && help[mode.helperType]) {
        found.push(help[mode.helperType]);
      } else if (help[mode.name]) {
        found.push(help[mode.name]);
      }
      for (var i = 0; i < help._global.length; i++) {
        var cur = help._global[i];
        if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
          found.push(cur.val);
      }
      return found;
    },

    getStateAfter: function(line, precise) {
      var doc = this.doc;
      line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
      return getStateBefore(this, line + 1, precise);
    },

    cursorCoords: function(start, mode) {
      var pos, range = this.doc.sel.primary();
      if (start == null) pos = range.head;
      else if (typeof start == "object") pos = clipPos(this.doc, start);
      else pos = start ? range.from() : range.to();
      return cursorCoords(this, pos, mode || "page");
    },

    charCoords: function(pos, mode) {
      return charCoords(this, clipPos(this.doc, pos), mode || "page");
    },

    coordsChar: function(coords, mode) {
      coords = fromCoordSystem(this, coords, mode || "page");
      return coordsChar(this, coords.left, coords.top);
    },

    lineAtHeight: function(height, mode) {
      height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
      return lineAtHeight(this.doc, height + this.display.viewOffset);
    },
    heightAtLine: function(line, mode) {
      var end = false, last = this.doc.first + this.doc.size - 1;
      if (line < this.doc.first) line = this.doc.first;
      else if (line > last) { line = last; end = true; }
      var lineObj = getLine(this.doc, line);
      return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top +
        (end ? this.doc.height - heightAtLine(lineObj) : 0);
    },

    defaultTextHeight: function() { return textHeight(this.display); },
    defaultCharWidth: function() { return charWidth(this.display); },

    setGutterMarker: methodOp(function(line, gutterID, value) {
      return changeLine(this.doc, line, "gutter", function(line) {
        var markers = line.gutterMarkers || (line.gutterMarkers = {});
        markers[gutterID] = value;
        if (!value && isEmpty(markers)) line.gutterMarkers = null;
        return true;
      });
    }),

    clearGutter: methodOp(function(gutterID) {
      var cm = this, doc = cm.doc, i = doc.first;
      doc.iter(function(line) {
        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
          line.gutterMarkers[gutterID] = null;
          regLineChange(cm, i, "gutter");
          if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
        }
        ++i;
      });
    }),

    addLineWidget: methodOp(function(handle, node, options) {
      return addLineWidget(this, handle, node, options);
    }),

    removeLineWidget: function(widget) { widget.clear(); },

    lineInfo: function(line) {
      if (typeof line == "number") {
        if (!isLine(this.doc, line)) return null;
        var n = line;
        line = getLine(this.doc, line);
        if (!line) return null;
      } else {
        var n = lineNo(line);
        if (n == null) return null;
      }
      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
              widgets: line.widgets};
    },

    getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};},

    addWidget: function(pos, node, scroll, vert, horiz) {
      var display = this.display;
      pos = cursorCoords(this, clipPos(this.doc, pos));
      var top = pos.bottom, left = pos.left;
      node.style.position = "absolute";
      display.sizer.appendChild(node);
      if (vert == "over") {
        top = pos.top;
      } else if (vert == "above" || vert == "near") {
        var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
        hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
        // Default to positioning above (if specified and possible); otherwise default to positioning below
        if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
          top = pos.top - node.offsetHeight;
        else if (pos.bottom + node.offsetHeight <= vspace)
          top = pos.bottom;
        if (left + node.offsetWidth > hspace)
          left = hspace - node.offsetWidth;
      }
      node.style.top = top + "px";
      node.style.left = node.style.right = "";
      if (horiz == "right") {
        left = display.sizer.clientWidth - node.offsetWidth;
        node.style.right = "0px";
      } else {
        if (horiz == "left") left = 0;
        else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
        node.style.left = left + "px";
      }
      if (scroll)
        scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
    },

    triggerOnKeyDown: methodOp(onKeyDown),
    triggerOnKeyPress: methodOp(onKeyPress),
    triggerOnKeyUp: onKeyUp,

    execCommand: function(cmd) {
      if (commands.hasOwnProperty(cmd))
        return commands[cmd](this);
    },

    findPosH: function(from, amount, unit, visually) {
      var dir = 1;
      if (amount < 0) { dir = -1; amount = -amount; }
      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
        cur = findPosH(this.doc, cur, dir, unit, visually);
        if (cur.hitSide) break;
      }
      return cur;
    },

    moveH: methodOp(function(dir, unit) {
      var cm = this;
      cm.extendSelectionsBy(function(range) {
        if (cm.display.shift || cm.doc.extend || range.empty())
          return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually);
        else
          return dir < 0 ? range.from() : range.to();
      }, sel_move);
    }),

    deleteH: methodOp(function(dir, unit) {
      var sel = this.doc.sel, doc = this.doc;
      if (sel.somethingSelected())
        doc.replaceSelection("", null, "+delete");
      else
        deleteNearSelection(this, function(range) {
          var other = findPosH(doc, range.head, dir, unit, false);
          return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other};
        });
    }),

    findPosV: function(from, amount, unit, goalColumn) {
      var dir = 1, x = goalColumn;
      if (amount < 0) { dir = -1; amount = -amount; }
      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
        var coords = cursorCoords(this, cur, "div");
        if (x == null) x = coords.left;
        else coords.left = x;
        cur = findPosV(this, coords, dir, unit);
        if (cur.hitSide) break;
      }
      return cur;
    },

    moveV: methodOp(function(dir, unit) {
      var cm = this, doc = this.doc, goals = [];
      var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected();
      doc.extendSelectionsBy(function(range) {
        if (collapse)
          return dir < 0 ? range.from() : range.to();
        var headPos = cursorCoords(cm, range.head, "div");
        if (range.goalColumn != null) headPos.left = range.goalColumn;
        goals.push(headPos.left);
        var pos = findPosV(cm, headPos, dir, unit);
        if (unit == "page" && range == doc.sel.primary())
          addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top);
        return pos;
      }, sel_move);
      if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++)
        doc.sel.ranges[i].goalColumn = goals[i];
    }),

    // Find the word at the given position (as returned by coordsChar).
    findWordAt: function(pos) {
      var doc = this.doc, line = getLine(doc, pos.line).text;
      var start = pos.ch, end = pos.ch;
      if (line) {
        var helper = this.getHelper(pos, "wordChars");
        if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
        var startChar = line.charAt(start);
        var check = isWordChar(startChar, helper)
          ? function(ch) { return isWordChar(ch, helper); }
          : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
          : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
        while (start > 0 && check(line.charAt(start - 1))) --start;
        while (end < line.length && check(line.charAt(end))) ++end;
      }
      return new Range(Pos(pos.line, start), Pos(pos.line, end));
    },

    toggleOverwrite: function(value) {
      if (value != null && value == this.state.overwrite) return;
      if (this.state.overwrite = !this.state.overwrite)
        addClass(this.display.cursorDiv, "CodeMirror-overwrite");
      else
        rmClass(this.display.cursorDiv, "CodeMirror-overwrite");

      signal(this, "overwriteToggle", this, this.state.overwrite);
    },
    hasFocus: function() { return activeElt() == this.display.input; },

    scrollTo: methodOp(function(x, y) {
      if (x != null || y != null) resolveScrollToPos(this);
      if (x != null) this.curOp.scrollLeft = x;
      if (y != null) this.curOp.scrollTop = y;
    }),
    getScrollInfo: function() {
      var scroller = this.display.scroller, co = scrollerCutOff;
      return {left: scroller.scrollLeft, top: scroller.scrollTop,
              height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
              clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
    },

    scrollIntoView: methodOp(function(range, margin) {
      if (range == null) {
        range = {from: this.doc.sel.primary().head, to: null};
        if (margin == null) margin = this.options.cursorScrollMargin;
      } else if (typeof range == "number") {
        range = {from: Pos(range, 0), to: null};
      } else if (range.from == null) {
        range = {from: range, to: null};
      }
      if (!range.to) range.to = range.from;
      range.margin = margin || 0;

      if (range.from.line != null) {
        resolveScrollToPos(this);
        this.curOp.scrollToPos = range;
      } else {
        var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left),
                                      Math.min(range.from.top, range.to.top) - range.margin,
                                      Math.max(range.from.right, range.to.right),
                                      Math.max(range.from.bottom, range.to.bottom) + range.margin);
        this.scrollTo(sPos.scrollLeft, sPos.scrollTop);
      }
    }),

    setSize: methodOp(function(width, height) {
      var cm = this;
      function interpret(val) {
        return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
      }
      if (width != null) cm.display.wrapper.style.width = interpret(width);
      if (height != null) cm.display.wrapper.style.height = interpret(height);
      if (cm.options.lineWrapping) clearLineMeasurementCache(this);
      var lineNo = cm.display.viewFrom;
      cm.doc.iter(lineNo, cm.display.viewTo, function(line) {
        if (line.widgets) for (var i = 0; i < line.widgets.length; i++)
          if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; }
        ++lineNo;
      });
      cm.curOp.forceUpdate = true;
      signal(cm, "refresh", this);
    }),

    operation: function(f){return runInOp(this, f);},

    refresh: methodOp(function() {
      var oldHeight = this.display.cachedTextHeight;
      regChange(this);
      this.curOp.forceUpdate = true;
      clearCaches(this);
      this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop);
      updateGutterSpace(this);
      if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
        estimateLineHeights(this);
      signal(this, "refresh", this);
    }),

    swapDoc: methodOp(function(doc) {
      var old = this.doc;
      old.cm = null;
      attachDoc(this, doc);
      clearCaches(this);
      resetInput(this);
      this.scrollTo(doc.scrollLeft, doc.scrollTop);
      this.curOp.forceScroll = true;
      signalLater(this, "swapDoc", this, old);
      return old;
    }),

    getInputField: function(){return this.display.input;},
    getWrapperElement: function(){return this.display.wrapper;},
    getScrollerElement: function(){return this.display.scroller;},
    getGutterElement: function(){return this.display.gutters;}
  };
  eventMixin(CodeMirror);

  // OPTION DEFAULTS

  // The default configuration options.
  var defaults = CodeMirror.defaults = {};
  // Functions to run when options are changed.
  var optionHandlers = CodeMirror.optionHandlers = {};

  function option(name, deflt, handle, notOnInit) {
    CodeMirror.defaults[name] = deflt;
    if (handle) optionHandlers[name] =
      notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
  }

  // Passed to option handlers when there is no old value.
  var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};

  // These two are, on init, called from the constructor because they
  // have to be initialized before the editor can start at all.
  option("value", "", function(cm, val) {
    cm.setValue(val);
  }, true);
  option("mode", null, function(cm, val) {
    cm.doc.modeOption = val;
    loadMode(cm);
  }, true);

  option("indentUnit", 2, loadMode, true);
  option("indentWithTabs", false);
  option("smartIndent", true);
  option("tabSize", 4, function(cm) {
    resetModeState(cm);
    clearCaches(cm);
    regChange(cm);
  }, true);
  option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) {
    cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
    cm.refresh();
  }, true);
  option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
  option("electricChars", true);
  option("rtlMoveVisually", !windows);
  option("wholeLineUpdateBefore", true);

  option("theme", "default", function(cm) {
    themeChanged(cm);
    guttersChanged(cm);
  }, true);
  option("keyMap", "default", keyMapChanged);
  option("extraKeys", null);

  option("lineWrapping", false, wrappingChanged, true);
  option("gutters", [], function(cm) {
    setGuttersForLineNumbers(cm.options);
    guttersChanged(cm);
  }, true);
  option("fixedGutter", true, function(cm, val) {
    cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
    cm.refresh();
  }, true);
  option("coverGutterNextToScrollbar", false, updateScrollbars, true);
  option("lineNumbers", false, function(cm) {
    setGuttersForLineNumbers(cm.options);
    guttersChanged(cm);
  }, true);
  option("firstLineNumber", 1, guttersChanged, true);
  option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
  option("showCursorWhenSelecting", false, updateSelection, true);

  option("resetSelectionOnContextMenu", true);

  option("readOnly", false, function(cm, val) {
    if (val == "nocursor") {
      onBlur(cm);
      cm.display.input.blur();
      cm.display.disabled = true;
    } else {
      cm.display.disabled = false;
      if (!val) resetInput(cm);
    }
  });
  option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true);
  option("dragDrop", true);

  option("cursorBlinkRate", 530);
  option("cursorScrollMargin", 0);
  option("cursorHeight", 1, updateSelection, true);
  option("singleCursorHeightPerLine", true, updateSelection, true);
  option("workTime", 100);
  option("workDelay", 100);
  option("flattenSpans", true, resetModeState, true);
  option("addModeClass", false, resetModeState, true);
  option("pollInterval", 100);
  option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;});
  option("historyEventDelay", 1250);
  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
  option("maxHighlightLength", 10000, resetModeState, true);
  option("moveInputWithCursor", true, function(cm, val) {
    if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
  });

  option("tabindex", null, function(cm, val) {
    cm.display.input.tabIndex = val || "";
  });
  option("autofocus", null);

  // MODE DEFINITION AND QUERYING

  // Known modes, by name and by MIME
  var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};

  // Extra arguments are stored as the mode's dependencies, which is
  // used by (legacy) mechanisms like loadmode.js to automatically
  // load a mode. (Preferred mechanism is the require/define calls.)
  CodeMirror.defineMode = function(name, mode) {
    if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
    if (arguments.length > 2)
      mode.dependencies = Array.prototype.slice.call(arguments, 2);
    modes[name] = mode;
  };

  CodeMirror.defineMIME = function(mime, spec) {
    mimeModes[mime] = spec;
  };

  // Given a MIME type, a {name, ...options} config object, or a name
  // string, return a mode config object.
  CodeMirror.resolveMode = function(spec) {
    if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
      spec = mimeModes[spec];
    } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
      var found = mimeModes[spec.name];
      if (typeof found == "string") found = {name: found};
      spec = createObj(found, spec);
      spec.name = found.name;
    } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
      return CodeMirror.resolveMode("application/xml");
    }
    if (typeof spec == "string") return {name: spec};
    else return spec || {name: "null"};
  };

  // Given a mode spec (anything that resolveMode accepts), find and
  // initialize an actual mode object.
  CodeMirror.getMode = function(options, spec) {
    var spec = CodeMirror.resolveMode(spec);
    var mfactory = modes[spec.name];
    if (!mfactory) return CodeMirror.getMode(options, "text/plain");
    var modeObj = mfactory(options, spec);
    if (modeExtensions.hasOwnProperty(spec.name)) {
      var exts = modeExtensions[spec.name];
      for (var prop in exts) {
        if (!exts.hasOwnProperty(prop)) continue;
        if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
        modeObj[prop] = exts[prop];
      }
    }
    modeObj.name = spec.name;
    if (spec.helperType) modeObj.helperType = spec.helperType;
    if (spec.modeProps) for (var prop in spec.modeProps)
      modeObj[prop] = spec.modeProps[prop];

    return modeObj;
  };

  // Minimal default mode.
  CodeMirror.defineMode("null", function() {
    return {token: function(stream) {stream.skipToEnd();}};
  });
  CodeMirror.defineMIME("text/plain", "null");

  // This can be used to attach properties to mode objects from
  // outside the actual mode definition.
  var modeExtensions = CodeMirror.modeExtensions = {};
  CodeMirror.extendMode = function(mode, properties) {
    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
    copyObj(properties, exts);
  };

  // EXTENSIONS

  CodeMirror.defineExtension = function(name, func) {
    CodeMirror.prototype[name] = func;
  };
  CodeMirror.defineDocExtension = function(name, func) {
    Doc.prototype[name] = func;
  };
  CodeMirror.defineOption = option;

  var initHooks = [];
  CodeMirror.defineInitHook = function(f) {initHooks.push(f);};

  var helpers = CodeMirror.helpers = {};
  CodeMirror.registerHelper = function(type, name, value) {
    if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []};
    helpers[type][name] = value;
  };
  CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
    CodeMirror.registerHelper(type, name, value);
    helpers[type]._global.push({pred: predicate, val: value});
  };

  // MODE STATE HANDLING

  // Utility functions for working with state. Exported because nested
  // modes need to do this for their inner modes.

  var copyState = CodeMirror.copyState = function(mode, state) {
    if (state === true) return state;
    if (mode.copyState) return mode.copyState(state);
    var nstate = {};
    for (var n in state) {
      var val = state[n];
      if (val instanceof Array) val = val.concat([]);
      nstate[n] = val;
    }
    return nstate;
  };

  var startState = CodeMirror.startState = function(mode, a1, a2) {
    return mode.startState ? mode.startState(a1, a2) : true;
  };

  // Given a mode and a state (for that mode), find the inner mode and
  // state at the position that the state refers to.
  CodeMirror.innerMode = function(mode, state) {
    while (mode.innerMode) {
      var info = mode.innerMode(state);
      if (!info || info.mode == mode) break;
      state = info.state;
      mode = info.mode;
    }
    return info || {mode: mode, state: state};
  };

  // STANDARD COMMANDS

  // Commands are parameter-less actions that can be performed on an
  // editor, mostly used for keybindings.
  var commands = CodeMirror.commands = {
    selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);},
    singleSelection: function(cm) {
      cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll);
    },
    killLine: function(cm) {
      deleteNearSelection(cm, function(range) {
        if (range.empty()) {
          var len = getLine(cm.doc, range.head.line).text.length;
          if (range.head.ch == len && range.head.line < cm.lastLine())
            return {from: range.head, to: Pos(range.head.line + 1, 0)};
          else
            return {from: range.head, to: Pos(range.head.line, len)};
        } else {
          return {from: range.from(), to: range.to()};
        }
      });
    },
    deleteLine: function(cm) {
      deleteNearSelection(cm, function(range) {
        return {from: Pos(range.from().line, 0),
                to: clipPos(cm.doc, Pos(range.to().line + 1, 0))};
      });
    },
    delLineLeft: function(cm) {
      deleteNearSelection(cm, function(range) {
        return {from: Pos(range.from().line, 0), to: range.from()};
      });
    },
    delWrappedLineLeft: function(cm) {
      deleteNearSelection(cm, function(range) {
        var top = cm.charCoords(range.head, "div").top + 5;
        var leftPos = cm.coordsChar({left: 0, top: top}, "div");
        return {from: leftPos, to: range.from()};
      });
    },
    delWrappedLineRight: function(cm) {
      deleteNearSelection(cm, function(range) {
        var top = cm.charCoords(range.head, "div").top + 5;
        var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
        return {from: range.from(), to: rightPos };
      });
    },
    undo: function(cm) {cm.undo();},
    redo: function(cm) {cm.redo();},
    undoSelection: function(cm) {cm.undoSelection();},
    redoSelection: function(cm) {cm.redoSelection();},
    goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
    goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
    goLineStart: function(cm) {
      cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); },
                            {origin: "+move", bias: 1});
    },
    goLineStartSmart: function(cm) {
      cm.extendSelectionsBy(function(range) {
        return lineStartSmart(cm, range.head);
      }, {origin: "+move", bias: 1});
    },
    goLineEnd: function(cm) {
      cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); },
                            {origin: "+move", bias: -1});
    },
    goLineRight: function(cm) {
      cm.extendSelectionsBy(function(range) {
        var top = cm.charCoords(range.head, "div").top + 5;
        return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
      }, sel_move);
    },
    goLineLeft: function(cm) {
      cm.extendSelectionsBy(function(range) {
        var top = cm.charCoords(range.head, "div").top + 5;
        return cm.coordsChar({left: 0, top: top}, "div");
      }, sel_move);
    },
    goLineLeftSmart: function(cm) {
      cm.extendSelectionsBy(function(range) {
        var top = cm.charCoords(range.head, "div").top + 5;
        var pos = cm.coordsChar({left: 0, top: top}, "div");
        if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head);
        return pos;
      }, sel_move);
    },
    goLineUp: function(cm) {cm.moveV(-1, "line");},
    goLineDown: function(cm) {cm.moveV(1, "line");},
    goPageUp: function(cm) {cm.moveV(-1, "page");},
    goPageDown: function(cm) {cm.moveV(1, "page");},
    goCharLeft: function(cm) {cm.moveH(-1, "char");},
    goCharRight: function(cm) {cm.moveH(1, "char");},
    goColumnLeft: function(cm) {cm.moveH(-1, "column");},
    goColumnRight: function(cm) {cm.moveH(1, "column");},
    goWordLeft: function(cm) {cm.moveH(-1, "word");},
    goGroupRight: function(cm) {cm.moveH(1, "group");},
    goGroupLeft: function(cm) {cm.moveH(-1, "group");},
    goWordRight: function(cm) {cm.moveH(1, "word");},
    delCharBefore: function(cm) {cm.deleteH(-1, "char");},
    delCharAfter: function(cm) {cm.deleteH(1, "char");},
    delWordBefore: function(cm) {cm.deleteH(-1, "word");},
    delWordAfter: function(cm) {cm.deleteH(1, "word");},
    delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
    delGroupAfter: function(cm) {cm.deleteH(1, "group");},
    indentAuto: function(cm) {cm.indentSelection("smart");},
    indentMore: function(cm) {cm.indentSelection("add");},
    indentLess: function(cm) {cm.indentSelection("subtract");},
    insertTab: function(cm) {cm.replaceSelection("\t");},
    insertSoftTab: function(cm) {
      var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;
      for (var i = 0; i < ranges.length; i++) {
        var pos = ranges[i].from();
        var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
        spaces.push(new Array(tabSize - col % tabSize + 1).join(" "));
      }
      cm.replaceSelections(spaces);
    },
    defaultTab: function(cm) {
      if (cm.somethingSelected()) cm.indentSelection("add");
      else cm.execCommand("insertTab");
    },
    transposeChars: function(cm) {
      runInOp(cm, function() {
        var ranges = cm.listSelections(), newSel = [];
        for (var i = 0; i < ranges.length; i++) {
          var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
          if (line) {
            if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1);
            if (cur.ch > 0) {
              cur = new Pos(cur.line, cur.ch + 1);
              cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
                              Pos(cur.line, cur.ch - 2), cur, "+transpose");
            } else if (cur.line > cm.doc.first) {
              var prev = getLine(cm.doc, cur.line - 1).text;
              if (prev)
                cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1),
                                Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose");
            }
          }
          newSel.push(new Range(cur, cur));
        }
        cm.setSelections(newSel);
      });
    },
    newlineAndIndent: function(cm) {
      runInOp(cm, function() {
        var len = cm.listSelections().length;
        for (var i = 0; i < len; i++) {
          var range = cm.listSelections()[i];
          cm.replaceRange("\n", range.anchor, range.head, "+input");
          cm.indentLine(range.from().line + 1, null, true);
          ensureCursorVisible(cm);
        }
      });
    },
    toggleOverwrite: function(cm) {cm.toggleOverwrite();}
  };

  // STANDARD KEYMAPS

  var keyMap = CodeMirror.keyMap = {};
  keyMap.basic = {
    "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
    "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
    "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
    "Tab": "defaultTab", "Shift-Tab": "indentAuto",
    "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
    "Esc": "singleSelection"
  };
  // Note that the save and find-related commands aren't defined by
  // default. User code or addons can define them. Unknown commands
  // are simply ignored.
  keyMap.pcDefault = {
    "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
    "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
    "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
    "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
    "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
    "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
    "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
    fallthrough: "basic"
  };
  keyMap.macDefault = {
    "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
    "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
    "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
    "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
    "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
    "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
    "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
    fallthrough: ["basic", "emacsy"]
  };
  // Very basic readline/emacs-style bindings, which are standard on Mac.
  keyMap.emacsy = {
    "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
    "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
    "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
    "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
  };
  keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;

  // KEYMAP DISPATCH

  function getKeyMap(val) {
    if (typeof val == "string") return keyMap[val];
    else return val;
  }

  // Given an array of keymaps and a key name, call handle on any
  // bindings found, until that returns a truthy value, at which point
  // we consider the key handled. Implements things like binding a key
  // to false stopping further handling and keymap fallthrough.
  var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) {
    function lookup(map) {
      map = getKeyMap(map);
      var found = map[name];
      if (found === false) return "stop";
      if (found != null && handle(found)) return true;
      if (map.nofallthrough) return "stop";

      var fallthrough = map.fallthrough;
      if (fallthrough == null) return false;
      if (Object.prototype.toString.call(fallthrough) != "[object Array]")
        return lookup(fallthrough);
      for (var i = 0; i < fallthrough.length; ++i) {
        var done = lookup(fallthrough[i]);
        if (done) return done;
      }
      return false;
    }

    for (var i = 0; i < maps.length; ++i) {
      var done = lookup(maps[i]);
      if (done) return done != "stop";
    }
  };

  // Modifier key presses don't count as 'real' key presses for the
  // purpose of keymap fallthrough.
  var isModifierKey = CodeMirror.isModifierKey = function(event) {
    var name = keyNames[event.keyCode];
    return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
  };

  // Look up the name of a key as indicated by an event object.
  var keyName = CodeMirror.keyName = function(event, noShift) {
    if (presto && event.keyCode == 34 && event["char"]) return false;
    var name = keyNames[event.keyCode];
    if (name == null || event.altGraphKey) return false;
    if (event.altKey) name = "Alt-" + name;
    if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
    if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
    if (!noShift && event.shiftKey) name = "Shift-" + name;
    return name;
  };

  // FROMTEXTAREA

  CodeMirror.fromTextArea = function(textarea, options) {
    if (!options) options = {};
    options.value = textarea.value;
    if (!options.tabindex && textarea.tabindex)
      options.tabindex = textarea.tabindex;
    if (!options.placeholder && textarea.placeholder)
      options.placeholder = textarea.placeholder;
    // Set autofocus to true if this textarea is focused, or if it has
    // autofocus and no other element is focused.
    if (options.autofocus == null) {
      var hasFocus = activeElt();
      options.autofocus = hasFocus == textarea ||
        textarea.getAttribute("autofocus") != null && hasFocus == document.body;
    }

    function save() {textarea.value = cm.getValue();}
    if (textarea.form) {
      on(textarea.form, "submit", save);
      // Deplorable hack to make the submit method do the right thing.
      if (!options.leaveSubmitMethodAlone) {
        var form = textarea.form, realSubmit = form.submit;
        try {
          var wrappedSubmit = form.submit = function() {
            save();
            form.submit = realSubmit;
            form.submit();
            form.submit = wrappedSubmit;
          };
        } catch(e) {}
      }
    }

    textarea.style.display = "none";
    var cm = CodeMirror(function(node) {
      textarea.parentNode.insertBefore(node, textarea.nextSibling);
    }, options);
    cm.save = save;
    cm.getTextArea = function() { return textarea; };
    cm.toTextArea = function() {
      cm.toTextArea = isNaN; // Prevent this from being ran twice
      save();
      textarea.parentNode.removeChild(cm.getWrapperElement());
      textarea.style.display = "";
      if (textarea.form) {
        off(textarea.form, "submit", save);
        if (typeof textarea.form.submit == "function")
          textarea.form.submit = realSubmit;
      }
    };
    return cm;
  };

  // STRING STREAM

  // Fed to the mode parsers, provides helper functions to make
  // parsers more succinct.

  var StringStream = CodeMirror.StringStream = function(string, tabSize) {
    this.pos = this.start = 0;
    this.string = string;
    this.tabSize = tabSize || 8;
    this.lastColumnPos = this.lastColumnValue = 0;
    this.lineStart = 0;
  };

  StringStream.prototype = {
    eol: function() {return this.pos >= this.string.length;},
    sol: function() {return this.pos == this.lineStart;},
    peek: function() {return this.string.charAt(this.pos) || undefined;},
    next: function() {
      if (this.pos < this.string.length)
        return this.string.charAt(this.pos++);
    },
    eat: function(match) {
      var ch = this.string.charAt(this.pos);
      if (typeof match == "string") var ok = ch == match;
      else var ok = ch && (match.test ? match.test(ch) : match(ch));
      if (ok) {++this.pos; return ch;}
    },
    eatWhile: function(match) {
      var start = this.pos;
      while (this.eat(match)){}
      return this.pos > start;
    },
    eatSpace: function() {
      var start = this.pos;
      while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
      return this.pos > start;
    },
    skipToEnd: function() {this.pos = this.string.length;},
    skipTo: function(ch) {
      var found = this.string.indexOf(ch, this.pos);
      if (found > -1) {this.pos = found; return true;}
    },
    backUp: function(n) {this.pos -= n;},
    column: function() {
      if (this.lastColumnPos < this.start) {
        this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
        this.lastColumnPos = this.start;
      }
      return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
    },
    indentation: function() {
      return countColumn(this.string, null, this.tabSize) -
        (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
    },
    match: function(pattern, consume, caseInsensitive) {
      if (typeof pattern == "string") {
        var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
        var substr = this.string.substr(this.pos, pattern.length);
        if (cased(substr) == cased(pattern)) {
          if (consume !== false) this.pos += pattern.length;
          return true;
        }
      } else {
        var match = this.string.slice(this.pos).match(pattern);
        if (match && match.index > 0) return null;
        if (match && consume !== false) this.pos += match[0].length;
        return match;
      }
    },
    current: function(){return this.string.slice(this.start, this.pos);},
    hideFirstChars: function(n, inner) {
      this.lineStart += n;
      try { return inner(); }
      finally { this.lineStart -= n; }
    }
  };

  // TEXTMARKERS

  // Created with markText and setBookmark methods. A TextMarker is a
  // handle that can be used to clear or find a marked position in the
  // document. Line objects hold arrays (markedSpans) containing
  // {from, to, marker} object pointing to such marker objects, and
  // indicating that such a marker is present on that line. Multiple
  // lines may point to the same marker when it spans across lines.
  // The spans will have null for their from/to properties when the
  // marker continues beyond the start/end of the line. Markers have
  // links back to the lines they currently touch.

  var TextMarker = CodeMirror.TextMarker = function(doc, type) {
    this.lines = [];
    this.type = type;
    this.doc = doc;
  };
  eventMixin(TextMarker);

  // Clear the marker.
  TextMarker.prototype.clear = function() {
    if (this.explicitlyCleared) return;
    var cm = this.doc.cm, withOp = cm && !cm.curOp;
    if (withOp) startOperation(cm);
    if (hasHandler(this, "clear")) {
      var found = this.find();
      if (found) signalLater(this, "clear", found.from, found.to);
    }
    var min = null, max = null;
    for (var i = 0; i < this.lines.length; ++i) {
      var line = this.lines[i];
      var span = getMarkedSpanFor(line.markedSpans, this);
      if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text");
      else if (cm) {
        if (span.to != null) max = lineNo(line);
        if (span.from != null) min = lineNo(line);
      }
      line.markedSpans = removeMarkedSpan(line.markedSpans, span);
      if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm)
        updateLineHeight(line, textHeight(cm.display));
    }
    if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
      var visual = visualLine(this.lines[i]), len = lineLength(visual);
      if (len > cm.display.maxLineLength) {
        cm.display.maxLine = visual;
        cm.display.maxLineLength = len;
        cm.display.maxLineChanged = true;
      }
    }

    if (min != null && cm && this.collapsed) regChange(cm, min, max + 1);
    this.lines.length = 0;
    this.explicitlyCleared = true;
    if (this.atomic && this.doc.cantEdit) {
      this.doc.cantEdit = false;
      if (cm) reCheckSelection(cm.doc);
    }
    if (cm) signalLater(cm, "markerCleared", cm, this);
    if (withOp) endOperation(cm);
    if (this.parent) this.parent.clear();
  };

  // Find the position of the marker in the document. Returns a {from,
  // to} object by default. Side can be passed to get a specific side
  // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
  // Pos objects returned contain a line object, rather than a line
  // number (used to prevent looking up the same line twice).
  TextMarker.prototype.find = function(side, lineObj) {
    if (side == null && this.type == "bookmark") side = 1;
    var from, to;
    for (var i = 0; i < this.lines.length; ++i) {
      var line = this.lines[i];
      var span = getMarkedSpanFor(line.markedSpans, this);
      if (span.from != null) {
        from = Pos(lineObj ? line : lineNo(line), span.from);
        if (side == -1) return from;
      }
      if (span.to != null) {
        to = Pos(lineObj ? line : lineNo(line), span.to);
        if (side == 1) return to;
      }
    }
    return from && {from: from, to: to};
  };

  // Signals that the marker's widget changed, and surrounding layout
  // should be recomputed.
  TextMarker.prototype.changed = function() {
    var pos = this.find(-1, true), widget = this, cm = this.doc.cm;
    if (!pos || !cm) return;
    runInOp(cm, function() {
      var line = pos.line, lineN = lineNo(pos.line);
      var view = findViewForLine(cm, lineN);
      if (view) {
        clearLineMeasurementCacheFor(view);
        cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;
      }
      cm.curOp.updateMaxLine = true;
      if (!lineIsHidden(widget.doc, line) && widget.height != null) {
        var oldHeight = widget.height;
        widget.height = null;
        var dHeight = widgetHeight(widget) - oldHeight;
        if (dHeight)
          updateLineHeight(line, line.height + dHeight);
      }
    });
  };

  TextMarker.prototype.attachLine = function(line) {
    if (!this.lines.length && this.doc.cm) {
      var op = this.doc.cm.curOp;
      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
        (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
    }
    this.lines.push(line);
  };
  TextMarker.prototype.detachLine = function(line) {
    this.lines.splice(indexOf(this.lines, line), 1);
    if (!this.lines.length && this.doc.cm) {
      var op = this.doc.cm.curOp;
      (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
    }
  };

  // Collapsed markers have unique ids, in order to be able to order
  // them, which is needed for uniquely determining an outer marker
  // when they overlap (they may nest, but not partially overlap).
  var nextMarkerId = 0;

  // Create a marker, wire it up to the right lines, and
  function markText(doc, from, to, options, type) {
    // Shared markers (across linked documents) are handled separately
    // (markTextShared will call out to this again, once per
    // document).
    if (options && options.shared) return markTextShared(doc, from, to, options, type);
    // Ensure we are in an operation.
    if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);

    var marker = new TextMarker(doc, type), diff = cmp(from, to);
    if (options) copyObj(options, marker, false);
    // Don't connect empty markers unless clearWhenEmpty is false
    if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
      return marker;
    if (marker.replacedWith) {
      // Showing up as a widget implies collapsed (widget replaces text)
      marker.collapsed = true;
      marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget");
      if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true;
      if (options.insertLeft) marker.widgetNode.insertLeft = true;
    }
    if (marker.collapsed) {
      if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
          from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
        throw new Error("Inserting collapsed marker partially overlapping an existing one");
      sawCollapsedSpans = true;
    }

    if (marker.addToHistory)
      addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN);

    var curLine = from.line, cm = doc.cm, updateMaxLine;
    doc.iter(curLine, to.line + 1, function(line) {
      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
        updateMaxLine = true;
      if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
      addMarkedSpan(line, new MarkedSpan(marker,
                                         curLine == from.line ? from.ch : null,
                                         curLine == to.line ? to.ch : null));
      ++curLine;
    });
    // lineIsHidden depends on the presence of the spans, so needs a second pass
    if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
      if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
    });

    if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });

    if (marker.readOnly) {
      sawReadOnlySpans = true;
      if (doc.history.done.length || doc.history.undone.length)
        doc.clearHistory();
    }
    if (marker.collapsed) {
      marker.id = ++nextMarkerId;
      marker.atomic = true;
    }
    if (cm) {
      // Sync editor state
      if (updateMaxLine) cm.curOp.updateMaxLine = true;
      if (marker.collapsed)
        regChange(cm, from.line, to.line + 1);
      else if (marker.className || marker.title || marker.startStyle || marker.endStyle)
        for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text");
      if (marker.atomic) reCheckSelection(cm.doc);
      signalLater(cm, "markerAdded", cm, marker);
    }
    return marker;
  }

  // SHARED TEXTMARKERS

  // A shared marker spans multiple linked documents. It is
  // implemented as a meta-marker-object controlling multiple normal
  // markers.
  var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) {
    this.markers = markers;
    this.primary = primary;
    for (var i = 0; i < markers.length; ++i)
      markers[i].parent = this;
  };
  eventMixin(SharedTextMarker);

  SharedTextMarker.prototype.clear = function() {
    if (this.explicitlyCleared) return;
    this.explicitlyCleared = true;
    for (var i = 0; i < this.markers.length; ++i)
      this.markers[i].clear();
    signalLater(this, "clear");
  };
  SharedTextMarker.prototype.find = function(side, lineObj) {
    return this.primary.find(side, lineObj);
  };

  function markTextShared(doc, from, to, options, type) {
    options = copyObj(options);
    options.shared = false;
    var markers = [markText(doc, from, to, options, type)], primary = markers[0];
    var widget = options.widgetNode;
    linkedDocs(doc, function(doc) {
      if (widget) options.widgetNode = widget.cloneNode(true);
      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
      for (var i = 0; i < doc.linked.length; ++i)
        if (doc.linked[i].isParent) return;
      primary = lst(markers);
    });
    return new SharedTextMarker(markers, primary);
  }

  function findSharedMarkers(doc) {
    return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())),
                         function(m) { return m.parent; });
  }

  function copySharedMarkers(doc, markers) {
    for (var i = 0; i < markers.length; i++) {
      var marker = markers[i], pos = marker.find();
      var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);
      if (cmp(mFrom, mTo)) {
        var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);
        marker.markers.push(subMark);
        subMark.parent = marker;
      }
    }
  }

  function detachSharedMarkers(markers) {
    for (var i = 0; i < markers.length; i++) {
      var marker = markers[i], linked = [marker.primary.doc];;
      linkedDocs(marker.primary.doc, function(d) { linked.push(d); });
      for (var j = 0; j < marker.markers.length; j++) {
        var subMarker = marker.markers[j];
        if (indexOf(linked, subMarker.doc) == -1) {
          subMarker.parent = null;
          marker.markers.splice(j--, 1);
        }
      }
    }
  }

  // TEXTMARKER SPANS

  function MarkedSpan(marker, from, to) {
    this.marker = marker;
    this.from = from; this.to = to;
  }

  // Search an array of spans for a span matching the given marker.
  function getMarkedSpanFor(spans, marker) {
    if (spans) for (var i = 0; i < spans.length; ++i) {
      var span = spans[i];
      if (span.marker == marker) return span;
    }
  }
  // Remove a span from an array, returning undefined if no spans are
  // left (we don't store arrays for lines without spans).
  function removeMarkedSpan(spans, span) {
    for (var r, i = 0; i < spans.length; ++i)
      if (spans[i] != span) (r || (r = [])).push(spans[i]);
    return r;
  }
  // Add a span to a line.
  function addMarkedSpan(line, span) {
    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
    span.marker.attachLine(line);
  }

  // Used for the algorithm that adjusts markers for a change in the
  // document. These functions cut an array of spans at a given
  // character position, returning an array of remaining chunks (or
  // undefined if nothing remains).
  function markedSpansBefore(old, startCh, isInsert) {
    if (old) for (var i = 0, nw; i < old.length; ++i) {
      var span = old[i], marker = span.marker;
      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
      if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
        (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));
      }
    }
    return nw;
  }
  function markedSpansAfter(old, endCh, isInsert) {
    if (old) for (var i = 0, nw; i < old.length; ++i) {
      var span = old[i], marker = span.marker;
      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
      if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
        (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,
                                              span.to == null ? null : span.to - endCh));
      }
    }
    return nw;
  }

  // Given a change object, compute the new set of marker spans that
  // cover the line in which the change took place. Removes spans
  // entirely within the change, reconnects spans belonging to the
  // same marker that appear on both sides of the change, and cuts off
  // spans partially within the change. Returns an array of span
  // arrays with one element for each line in (after) the change.
  function stretchSpansOverChange(doc, change) {
    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
    if (!oldFirst && !oldLast) return null;

    var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;
    // Get the spans that 'stick out' on both sides
    var first = markedSpansBefore(oldFirst, startCh, isInsert);
    var last = markedSpansAfter(oldLast, endCh, isInsert);

    // Next, merge those two ends
    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
    if (first) {
      // Fix up .to properties of first
      for (var i = 0; i < first.length; ++i) {
        var span = first[i];
        if (span.to == null) {
          var found = getMarkedSpanFor(last, span.marker);
          if (!found) span.to = startCh;
          else if (sameLine) span.to = found.to == null ? null : found.to + offset;
        }
      }
    }
    if (last) {
      // Fix up .from in last (or move them into first in case of sameLine)
      for (var i = 0; i < last.length; ++i) {
        var span = last[i];
        if (span.to != null) span.to += offset;
        if (span.from == null) {
          var found = getMarkedSpanFor(first, span.marker);
          if (!found) {
            span.from = offset;
            if (sameLine) (first || (first = [])).push(span);
          }
        } else {
          span.from += offset;
          if (sameLine) (first || (first = [])).push(span);
        }
      }
    }
    // Make sure we didn't create any zero-length spans
    if (first) first = clearEmptySpans(first);
    if (last && last != first) last = clearEmptySpans(last);

    var newMarkers = [first];
    if (!sameLine) {
      // Fill gap with whole-line-spans
      var gap = change.text.length - 2, gapMarkers;
      if (gap > 0 && first)
        for (var i = 0; i < first.length; ++i)
          if (first[i].to == null)
            (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null));
      for (var i = 0; i < gap; ++i)
        newMarkers.push(gapMarkers);
      newMarkers.push(last);
    }
    return newMarkers;
  }

  // Remove spans that are empty and don't have a clearWhenEmpty
  // option of false.
  function clearEmptySpans(spans) {
    for (var i = 0; i < spans.length; ++i) {
      var span = spans[i];
      if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
        spans.splice(i--, 1);
    }
    if (!spans.length) return null;
    return spans;
  }

  // Used for un/re-doing changes from the history. Combines the
  // result of computing the existing spans with the set of spans that
  // existed in the history (so that deleting around a span and then
  // undoing brings back the span).
  function mergeOldSpans(doc, change) {
    var old = getOldSpans(doc, change);
    var stretched = stretchSpansOverChange(doc, change);
    if (!old) return stretched;
    if (!stretched) return old;

    for (var i = 0; i < old.length; ++i) {
      var oldCur = old[i], stretchCur = stretched[i];
      if (oldCur && stretchCur) {
        spans: for (var j = 0; j < stretchCur.length; ++j) {
          var span = stretchCur[j];
          for (var k = 0; k < oldCur.length; ++k)
            if (oldCur[k].marker == span.marker) continue spans;
          oldCur.push(span);
        }
      } else if (stretchCur) {
        old[i] = stretchCur;
      }
    }
    return old;
  }

  // Used to 'clip' out readOnly ranges when making a change.
  function removeReadOnlyRanges(doc, from, to) {
    var markers = null;
    doc.iter(from.line, to.line + 1, function(line) {
      if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
        var mark = line.markedSpans[i].marker;
        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
          (markers || (markers = [])).push(mark);
      }
    });
    if (!markers) return null;
    var parts = [{from: from, to: to}];
    for (var i = 0; i < markers.length; ++i) {
      var mk = markers[i], m = mk.find(0);
      for (var j = 0; j < parts.length; ++j) {
        var p = parts[j];
        if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue;
        var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);
        if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
          newParts.push({from: p.from, to: m.from});
        if (dto > 0 || !mk.inclusiveRight && !dto)
          newParts.push({from: m.to, to: p.to});
        parts.splice.apply(parts, newParts);
        j += newParts.length - 1;
      }
    }
    return parts;
  }

  // Connect or disconnect spans from a line.
  function detachMarkedSpans(line) {
    var spans = line.markedSpans;
    if (!spans) return;
    for (var i = 0; i < spans.length; ++i)
      spans[i].marker.detachLine(line);
    line.markedSpans = null;
  }
  function attachMarkedSpans(line, spans) {
    if (!spans) return;
    for (var i = 0; i < spans.length; ++i)
      spans[i].marker.attachLine(line);
    line.markedSpans = spans;
  }

  // Helpers used when computing which overlapping collapsed span
  // counts as the larger one.
  function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
  function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }

  // Returns a number indicating which of two overlapping collapsed
  // spans is larger (and thus includes the other). Falls back to
  // comparing ids when the spans cover exactly the same range.
  function compareCollapsedMarkers(a, b) {
    var lenDiff = a.lines.length - b.lines.length;
    if (lenDiff != 0) return lenDiff;
    var aPos = a.find(), bPos = b.find();
    var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
    if (fromCmp) return -fromCmp;
    var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
    if (toCmp) return toCmp;
    return b.id - a.id;
  }

  // Find out whether a line ends or starts in a collapsed span. If
  // so, return the marker for that span.
  function collapsedSpanAtSide(line, start) {
    var sps = sawCollapsedSpans && line.markedSpans, found;
    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
      sp = sps[i];
      if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
          (!found || compareCollapsedMarkers(found, sp.marker) < 0))
        found = sp.marker;
    }
    return found;
  }
  function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
  function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }

  // Test whether there exists a collapsed span that partially
  // overlaps (covers the start or end, but not both) of a new span.
  // Such overlap is not allowed.
  function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
    var line = getLine(doc, lineNo);
    var sps = sawCollapsedSpans && line.markedSpans;
    if (sps) for (var i = 0; i < sps.length; ++i) {
      var sp = sps[i];
      if (!sp.marker.collapsed) continue;
      var found = sp.marker.find(0);
      var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
      var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
      if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
      if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) ||
          fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight)))
        return true;
    }
  }

  // A visual line is a line as drawn on the screen. Folding, for
  // example, can cause multiple logical lines to appear on the same
  // visual line. This finds the start of the visual line that the
  // given line is part of (usually that is the line itself).
  function visualLine(line) {
    var merged;
    while (merged = collapsedSpanAtStart(line))
      line = merged.find(-1, true).line;
    return line;
  }

  // Returns an array of logical lines that continue the visual line
  // started by the argument, or undefined if there are no such lines.
  function visualLineContinued(line) {
    var merged, lines;
    while (merged = collapsedSpanAtEnd(line)) {
      line = merged.find(1, true).line;
      (lines || (lines = [])).push(line);
    }
    return lines;
  }

  // Get the line number of the start of the visual line that the
  // given line number is part of.
  function visualLineNo(doc, lineN) {
    var line = getLine(doc, lineN), vis = visualLine(line);
    if (line == vis) return lineN;
    return lineNo(vis);
  }
  // Get the line number of the start of the next visual line after
  // the given line.
  function visualLineEndNo(doc, lineN) {
    if (lineN > doc.lastLine()) return lineN;
    var line = getLine(doc, lineN), merged;
    if (!lineIsHidden(doc, line)) return lineN;
    while (merged = collapsedSpanAtEnd(line))
      line = merged.find(1, true).line;
    return lineNo(line) + 1;
  }

  // Compute whether a line is hidden. Lines count as hidden when they
  // are part of a visual line that starts with another line, or when
  // they are entirely covered by collapsed, non-widget span.
  function lineIsHidden(doc, line) {
    var sps = sawCollapsedSpans && line.markedSpans;
    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
      sp = sps[i];
      if (!sp.marker.collapsed) continue;
      if (sp.from == null) return true;
      if (sp.marker.widgetNode) continue;
      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
        return true;
    }
  }
  function lineIsHiddenInner(doc, line, span) {
    if (span.to == null) {
      var end = span.marker.find(1, true);
      return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker));
    }
    if (span.marker.inclusiveRight && span.to == line.text.length)
      return true;
    for (var sp, i = 0; i < line.markedSpans.length; ++i) {
      sp = line.markedSpans[i];
      if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
          (sp.to == null || sp.to != span.from) &&
          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
          lineIsHiddenInner(doc, line, sp)) return true;
    }
  }

  // LINE WIDGETS

  // Line widgets are block elements displayed above or below a line.

  var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
    if (options) for (var opt in options) if (options.hasOwnProperty(opt))
      this[opt] = options[opt];
    this.cm = cm;
    this.node = node;
  };
  eventMixin(LineWidget);

  function adjustScrollWhenAboveVisible(cm, line, diff) {
    if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
      addToScrollPos(cm, null, diff);
  }

  LineWidget.prototype.clear = function() {
    var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
    if (no == null || !ws) return;
    for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
    if (!ws.length) line.widgets = null;
    var height = widgetHeight(this);
    runInOp(cm, function() {
      adjustScrollWhenAboveVisible(cm, line, -height);
      regLineChange(cm, no, "widget");
      updateLineHeight(line, Math.max(0, line.height - height));
    });
  };
  LineWidget.prototype.changed = function() {
    var oldH = this.height, cm = this.cm, line = this.line;
    this.height = null;
    var diff = widgetHeight(this) - oldH;
    if (!diff) return;
    runInOp(cm, function() {
      cm.curOp.forceUpdate = true;
      adjustScrollWhenAboveVisible(cm, line, diff);
      updateLineHeight(line, line.height + diff);
    });
  };

  function widgetHeight(widget) {
    if (widget.height != null) return widget.height;
    if (!contains(document.body, widget.node)) {
      var parentStyle = "position: relative;";
      if (widget.coverGutter)
        parentStyle += "margin-left: -" + widget.cm.getGutterElement().offsetWidth + "px;";
      removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
    }
    return widget.height = widget.node.offsetHeight;
  }

  function addLineWidget(cm, handle, node, options) {
    var widget = new LineWidget(cm, node, options);
    if (widget.noHScroll) cm.display.alignWidgets = true;
    changeLine(cm.doc, handle, "widget", function(line) {
      var widgets = line.widgets || (line.widgets = []);
      if (widget.insertAt == null) widgets.push(widget);
      else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
      widget.line = line;
      if (!lineIsHidden(cm.doc, line)) {
        var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
        updateLineHeight(line, line.height + widgetHeight(widget));
        if (aboveVisible) addToScrollPos(cm, null, widget.height);
        cm.curOp.forceUpdate = true;
      }
      return true;
    });
    return widget;
  }

  // LINE DATA STRUCTURE

  // Line objects. These hold state related to a line, including
  // highlighting info (the styles array).
  var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
    this.text = text;
    attachMarkedSpans(this, markedSpans);
    this.height = estimateHeight ? estimateHeight(this) : 1;
  };
  eventMixin(Line);
  Line.prototype.lineNo = function() { return lineNo(this); };

  // Change the content (text, markers) of a line. Automatically
  // invalidates cached information and tries to re-estimate the
  // line's height.
  function updateLine(line, text, markedSpans, estimateHeight) {
    line.text = text;
    if (line.stateAfter) line.stateAfter = null;
    if (line.styles) line.styles = null;
    if (line.order != null) line.order = null;
    detachMarkedSpans(line);
    attachMarkedSpans(line, markedSpans);
    var estHeight = estimateHeight ? estimateHeight(line) : 1;
    if (estHeight != line.height) updateLineHeight(line, estHeight);
  }

  // Detach a line from the document tree and its markers.
  function cleanUpLine(line) {
    line.parent = null;
    detachMarkedSpans(line);
  }

  function extractLineClasses(type, output) {
    if (type) for (;;) {
      var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/);
      if (!lineClass) break;
      type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);
      var prop = lineClass[1] ? "bgClass" : "textClass";
      if (output[prop] == null)
        output[prop] = lineClass[2];
      else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop]))
        output[prop] += " " + lineClass[2];
    }
    return type;
  }

  function callBlankLine(mode, state) {
    if (mode.blankLine) return mode.blankLine(state);
    if (!mode.innerMode) return;
    var inner = CodeMirror.innerMode(mode, state);
    if (inner.mode.blankLine) return inner.mode.blankLine(inner.state);
  }

  function readToken(mode, stream, state) {
    for (var i = 0; i < 10; i++) {
      var style = mode.token(stream, state);
      if (stream.pos > stream.start) return style;
    }
    throw new Error("Mode " + mode.name + " failed to advance stream.");
  }

  // Run the given mode's parser over a line, calling f for each token.
  function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) {
    var flattenSpans = mode.flattenSpans;
    if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
    var curStart = 0, curStyle = null;
    var stream = new StringStream(text, cm.options.tabSize), style;
    if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses);
    while (!stream.eol()) {
      if (stream.pos > cm.options.maxHighlightLength) {
        flattenSpans = false;
        if (forceToEnd) processLine(cm, text, state, stream.pos);
        stream.pos = text.length;
        style = null;
      } else {
        style = extractLineClasses(readToken(mode, stream, state), lineClasses);
      }
      if (cm.options.addModeClass) {
        var mName = CodeMirror.innerMode(mode, state).mode.name;
        if (mName) style = "m-" + (style ? mName + " " + style : mName);
      }
      if (!flattenSpans || curStyle != style) {
        if (curStart < stream.start) f(stream.start, curStyle);
        curStart = stream.start; curStyle = style;
      }
      stream.start = stream.pos;
    }
    while (curStart < stream.pos) {
      // Webkit seems to refuse to render text nodes longer than 57444 characters
      var pos = Math.min(stream.pos, curStart + 50000);
      f(pos, curStyle);
      curStart = pos;
    }
  }

  // Compute a style array (an array starting with a mode generation
  // -- for invalidation -- followed by pairs of end positions and
  // style strings), which is used to highlight the tokens on the
  // line.
  function highlightLine(cm, line, state, forceToEnd) {
    // A styles array always starts with a number identifying the
    // mode/overlays that it is based on (for easy invalidation).
    var st = [cm.state.modeGen], lineClasses = {};
    // Compute the base array of styles
    runMode(cm, line.text, cm.doc.mode, state, function(end, style) {
      st.push(end, style);
    }, lineClasses, forceToEnd);

    // Run overlays, adjust style array.
    for (var o = 0; o < cm.state.overlays.length; ++o) {
      var overlay = cm.state.overlays[o], i = 1, at = 0;
      runMode(cm, line.text, overlay.mode, true, function(end, style) {
        var start = i;
        // Ensure there's a token end at the current position, and that i points at it
        while (at < end) {
          var i_end = st[i];
          if (i_end > end)
            st.splice(i, 1, end, st[i+1], i_end);
          i += 2;
          at = Math.min(end, i_end);
        }
        if (!style) return;
        if (overlay.opaque) {
          st.splice(start, i - start, end, "cm-overlay " + style);
          i = start + 2;
        } else {
          for (; start < i; start += 2) {
            var cur = st[start+1];
            st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
          }
        }
      }, lineClasses);
    }

    return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null};
  }

  function getLineStyles(cm, line) {
    if (!line.styles || line.styles[0] != cm.state.modeGen) {
      var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
      line.styles = result.styles;
      if (result.classes) line.styleClasses = result.classes;
      else if (line.styleClasses) line.styleClasses = null;
    }
    return line.styles;
  }

  // Lightweight form of highlight -- proceed over this line and
  // update state, but don't save a style array. Used for lines that
  // aren't currently visible.
  function processLine(cm, text, state, startAt) {
    var mode = cm.doc.mode;
    var stream = new StringStream(text, cm.options.tabSize);
    stream.start = stream.pos = startAt || 0;
    if (text == "") callBlankLine(mode, state);
    while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
      readToken(mode, stream, state);
      stream.start = stream.pos;
    }
  }

  // Convert a style as returned by a mode (either null, or a string
  // containing one or more styles) to a CSS style. This is cached,
  // and also looks for line-wide styles.
  var styleToClassCache = {}, styleToClassCacheWithMode = {};
  function interpretTokenStyle(style, options) {
    if (!style || /^\s*$/.test(style)) return null;
    var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
    return cache[style] ||
      (cache[style] = style.replace(/\S+/g, "cm-$&"));
  }

  // Render the DOM representation of the text of a line. Also builds
  // up a 'line map', which points at the DOM nodes that represent
  // specific stretches of text, and is used by the measuring code.
  // The returned object contains the DOM node, this map, and
  // information about line-wide styles that were set by the mode.
  function buildLineContent(cm, lineView) {
    // The padding-right forces the element to have a 'border', which
    // is needed on Webkit to be able to get line-level bounding
    // rectangles for it (in measureChar).
    var content = elt("span", null, null, webkit ? "padding-right: .1px" : null);
    var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm};
    lineView.measure = {};

    // Iterate over the logical lines that make up this visual line.
    for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
      var line = i ? lineView.rest[i - 1] : lineView.line, order;
      builder.pos = 0;
      builder.addToken = buildToken;
      // Optionally wire in some hacks into the token-rendering
      // algorithm, to deal with browser quirks.
      if ((ie || webkit) && cm.getOption("lineWrapping"))
        builder.addToken = buildTokenSplitSpaces(builder.addToken);
      if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
        builder.addToken = buildTokenBadBidi(builder.addToken, order);
      builder.map = [];
      insertLineContent(line, builder, getLineStyles(cm, line));
      if (line.styleClasses) {
        if (line.styleClasses.bgClass)
          builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "");
        if (line.styleClasses.textClass)
          builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "");
      }

      // Ensure at least a single node is present, for measuring.
      if (builder.map.length == 0)
        builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure)));

      // Store the map and a cache object for the current logical line
      if (i == 0) {
        lineView.measure.map = builder.map;
        lineView.measure.cache = {};
      } else {
        (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map);
        (lineView.measure.caches || (lineView.measure.caches = [])).push({});
      }
    }

    signal(cm, "renderLine", cm, lineView.line, builder.pre);
    if (builder.pre.className)
      builder.textClass = joinClasses(builder.pre.className, builder.textClass || "");
    return builder;
  }

  function defaultSpecialCharPlaceholder(ch) {
    var token = elt("span", "\u2022", "cm-invalidchar");
    token.title = "\\u" + ch.charCodeAt(0).toString(16);
    return token;
  }

  // Build up the DOM representation for a single token, and add it to
  // the line map. Takes care to render special characters separately.
  function buildToken(builder, text, style, startStyle, endStyle, title) {
    if (!text) return;
    var special = builder.cm.options.specialChars, mustWrap = false;
    if (!special.test(text)) {
      builder.col += text.length;
      var content = document.createTextNode(text);
      builder.map.push(builder.pos, builder.pos + text.length, content);
      if (ie && ie_version < 9) mustWrap = true;
      builder.pos += text.length;
    } else {
      var content = document.createDocumentFragment(), pos = 0;
      while (true) {
        special.lastIndex = pos;
        var m = special.exec(text);
        var skipped = m ? m.index - pos : text.length - pos;
        if (skipped) {
          var txt = document.createTextNode(text.slice(pos, pos + skipped));
          if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
          else content.appendChild(txt);
          builder.map.push(builder.pos, builder.pos + skipped, txt);
          builder.col += skipped;
          builder.pos += skipped;
        }
        if (!m) break;
        pos += skipped + 1;
        if (m[0] == "\t") {
          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
          var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
          builder.col += tabWidth;
        } else {
          var txt = builder.cm.options.specialCharPlaceholder(m[0]);
          if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
          else content.appendChild(txt);
          builder.col += 1;
        }
        builder.map.push(builder.pos, builder.pos + 1, txt);
        builder.pos++;
      }
    }
    if (style || startStyle || endStyle || mustWrap) {
      var fullStyle = style || "";
      if (startStyle) fullStyle += startStyle;
      if (endStyle) fullStyle += endStyle;
      var token = elt("span", [content], fullStyle);
      if (title) token.title = title;
      return builder.content.appendChild(token);
    }
    builder.content.appendChild(content);
  }

  function buildTokenSplitSpaces(inner) {
    function split(old) {
      var out = " ";
      for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
      out += " ";
      return out;
    }
    return function(builder, text, style, startStyle, endStyle, title) {
      inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
    };
  }

  // Work around nonsense dimensions being reported for stretches of
  // right-to-left text.
  function buildTokenBadBidi(inner, order) {
    return function(builder, text, style, startStyle, endStyle, title) {
      style = style ? style + " cm-force-border" : "cm-force-border";
      var start = builder.pos, end = start + text.length;
      for (;;) {
        // Find the part that overlaps with the start of this text
        for (var i = 0; i < order.length; i++) {
          var part = order[i];
          if (part.to > start && part.from <= start) break;
        }
        if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title);
        inner(builder, text.slice(0, part.to - start), style, startStyle, null, title);
        startStyle = null;
        text = text.slice(part.to - start);
        start = part.to;
      }
    };
  }

  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
    var widget = !ignoreWidget && marker.widgetNode;
    if (widget) {
      builder.map.push(builder.pos, builder.pos + size, widget);
      builder.content.appendChild(widget);
    }
    builder.pos += size;
  }

  // Outputs a number of spans to make up a line, taking highlighting
  // and marked text into account.
  function insertLineContent(line, builder, styles) {
    var spans = line.markedSpans, allText = line.text, at = 0;
    if (!spans) {
      for (var i = 1; i < styles.length; i+=2)
        builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options));
      return;
    }

    var len = allText.length, pos = 0, i = 1, text = "", style;
    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
    for (;;) {
      if (nextChange == pos) { // Update current marker set
        spanStyle = spanEndStyle = spanStartStyle = title = "";
        collapsed = null; nextChange = Infinity;
        var foundBookmarks = [];
        for (var j = 0; j < spans.length; ++j) {
          var sp = spans[j], m = sp.marker;
          if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
            if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
            if (m.className) spanStyle += " " + m.className;
            if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
            if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
            if (m.title && !title) title = m.title;
            if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
              collapsed = sp;
          } else if (sp.from > pos && nextChange > sp.from) {
            nextChange = sp.from;
          }
          if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m);
        }
        if (collapsed && (collapsed.from || 0) == pos) {
          buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
                             collapsed.marker, collapsed.from == null);
          if (collapsed.to == null) return;
        }
        if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
          buildCollapsedSpan(builder, 0, foundBookmarks[j]);
      }
      if (pos >= len) break;

      var upto = Math.min(len, nextChange);
      while (true) {
        if (text) {
          var end = pos + text.length;
          if (!collapsed) {
            var tokenText = end > upto ? text.slice(0, upto - pos) : text;
            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
          }
          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
          pos = end;
          spanStartStyle = "";
        }
        text = allText.slice(at, at = styles[i++]);
        style = interpretTokenStyle(styles[i++], builder.cm.options);
      }
    }
  }

  // DOCUMENT DATA STRUCTURE

  // By default, updates that start and end at the beginning of a line
  // are treated specially, in order to make the association of line
  // widgets and marker elements with the text behave more intuitive.
  function isWholeLineUpdate(doc, change) {
    return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
      (!doc.cm || doc.cm.options.wholeLineUpdateBefore);
  }

  // Perform a change on the document data structure.
  function updateDoc(doc, change, markedSpans, estimateHeight) {
    function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
    function update(line, text, spans) {
      updateLine(line, text, spans, estimateHeight);
      signalLater(line, "change", line, change);
    }

    var from = change.from, to = change.to, text = change.text;
    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;

    // Adjust the line structure
    if (isWholeLineUpdate(doc, change)) {
      // This is a whole-line replace. Treated specially to make
      // sure line objects move the way they are supposed to.
      for (var i = 0, added = []; i < text.length - 1; ++i)
        added.push(new Line(text[i], spansFor(i), estimateHeight));
      update(lastLine, lastLine.text, lastSpans);
      if (nlines) doc.remove(from.line, nlines);
      if (added.length) doc.insert(from.line, added);
    } else if (firstLine == lastLine) {
      if (text.length == 1) {
        update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
      } else {
        for (var added = [], i = 1; i < text.length - 1; ++i)
          added.push(new Line(text[i], spansFor(i), estimateHeight));
        added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
        update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
        doc.insert(from.line + 1, added);
      }
    } else if (text.length == 1) {
      update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
      doc.remove(from.line + 1, nlines);
    } else {
      update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
      update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
      for (var i = 1, added = []; i < text.length - 1; ++i)
        added.push(new Line(text[i], spansFor(i), estimateHeight));
      if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
      doc.insert(from.line + 1, added);
    }

    signalLater(doc, "change", doc, change);
  }

  // The document is represented as a BTree consisting of leaves, with
  // chunk of lines in them, and branches, with up to ten leaves or
  // other branch nodes below them. The top node is always a branch
  // node, and is the document object itself (meaning it has
  // additional methods and properties).
  //
  // All nodes have parent links. The tree is used both to go from
  // line numbers to line objects, and to go from objects to numbers.
  // It also indexes by height, and is used to convert between height
  // and line object, and to find the total height of the document.
  //
  // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html

  function LeafChunk(lines) {
    this.lines = lines;
    this.parent = null;
    for (var i = 0, height = 0; i < lines.length; ++i) {
      lines[i].parent = this;
      height += lines[i].height;
    }
    this.height = height;
  }

  LeafChunk.prototype = {
    chunkSize: function() { return this.lines.length; },
    // Remove the n lines at offset 'at'.
    removeInner: function(at, n) {
      for (var i = at, e = at + n; i < e; ++i) {
        var line = this.lines[i];
        this.height -= line.height;
        cleanUpLine(line);
        signalLater(line, "delete");
      }
      this.lines.splice(at, n);
    },
    // Helper used to collapse a small branch into a single leaf.
    collapse: function(lines) {
      lines.push.apply(lines, this.lines);
    },
    // Insert the given array of lines at offset 'at', count them as
    // having the given height.
    insertInner: function(at, lines, height) {
      this.height += height;
      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
      for (var i = 0; i < lines.length; ++i) lines[i].parent = this;
    },
    // Used to iterate over a part of the tree.
    iterN: function(at, n, op) {
      for (var e = at + n; at < e; ++at)
        if (op(this.lines[at])) return true;
    }
  };

  function BranchChunk(children) {
    this.children = children;
    var size = 0, height = 0;
    for (var i = 0; i < children.length; ++i) {
      var ch = children[i];
      size += ch.chunkSize(); height += ch.height;
      ch.parent = this;
    }
    this.size = size;
    this.height = height;
    this.parent = null;
  }

  BranchChunk.prototype = {
    chunkSize: function() { return this.size; },
    removeInner: function(at, n) {
      this.size -= n;
      for (var i = 0; i < this.children.length; ++i) {
        var child = this.children[i], sz = child.chunkSize();
        if (at < sz) {
          var rm = Math.min(n, sz - at), oldHeight = child.height;
          child.removeInner(at, rm);
          this.height -= oldHeight - child.height;
          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
          if ((n -= rm) == 0) break;
          at = 0;
        } else at -= sz;
      }
      // If the result is smaller than 25 lines, ensure that it is a
      // single leaf node.
      if (this.size - n < 25 &&
          (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
        var lines = [];
        this.collapse(lines);
        this.children = [new LeafChunk(lines)];
        this.children[0].parent = this;
      }
    },
    collapse: function(lines) {
      for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines);
    },
    insertInner: function(at, lines, height) {
      this.size += lines.length;
      this.height += height;
      for (var i = 0; i < this.children.length; ++i) {
        var child = this.children[i], sz = child.chunkSize();
        if (at <= sz) {
          child.insertInner(at, lines, height);
          if (child.lines && child.lines.length > 50) {
            while (child.lines.length > 50) {
              var spilled = child.lines.splice(child.lines.length - 25, 25);
              var newleaf = new LeafChunk(spilled);
              child.height -= newleaf.height;
              this.children.splice(i + 1, 0, newleaf);
              newleaf.parent = this;
            }
            this.maybeSpill();
          }
          break;
        }
        at -= sz;
      }
    },
    // When a node has grown, check whether it should be split.
    maybeSpill: function() {
      if (this.children.length <= 10) return;
      var me = this;
      do {
        var spilled = me.children.splice(me.children.length - 5, 5);
        var sibling = new BranchChunk(spilled);
        if (!me.parent) { // Become the parent node
          var copy = new BranchChunk(me.children);
          copy.parent = me;
          me.children = [copy, sibling];
          me = copy;
        } else {
          me.size -= sibling.size;
          me.height -= sibling.height;
          var myIndex = indexOf(me.parent.children, me);
          me.parent.children.splice(myIndex + 1, 0, sibling);
        }
        sibling.parent = me.parent;
      } while (me.children.length > 10);
      me.parent.maybeSpill();
    },
    iterN: function(at, n, op) {
      for (var i = 0; i < this.children.length; ++i) {
        var child = this.children[i], sz = child.chunkSize();
        if (at < sz) {
          var used = Math.min(n, sz - at);
          if (child.iterN(at, used, op)) return true;
          if ((n -= used) == 0) break;
          at = 0;
        } else at -= sz;
      }
    }
  };

  var nextDocId = 0;
  var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
    if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
    if (firstLine == null) firstLine = 0;

    BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
    this.first = firstLine;
    this.scrollTop = this.scrollLeft = 0;
    this.cantEdit = false;
    this.cleanGeneration = 1;
    this.frontier = firstLine;
    var start = Pos(firstLine, 0);
    this.sel = simpleSelection(start);
    this.history = new History(null);
    this.id = ++nextDocId;
    this.modeOption = mode;

    if (typeof text == "string") text = splitLines(text);
    updateDoc(this, {from: start, to: start, text: text});
    setSelection(this, simpleSelection(start), sel_dontScroll);
  };

  Doc.prototype = createObj(BranchChunk.prototype, {
    constructor: Doc,
    // Iterate over the document. Supports two forms -- with only one
    // argument, it calls that for each line in the document. With
    // three, it iterates over the range given by the first two (with
    // the second being non-inclusive).
    iter: function(from, to, op) {
      if (op) this.iterN(from - this.first, to - from, op);
      else this.iterN(this.first, this.first + this.size, from);
    },

    // Non-public interface for adding and removing lines.
    insert: function(at, lines) {
      var height = 0;
      for (var i = 0; i < lines.length; ++i) height += lines[i].height;
      this.insertInner(at - this.first, lines, height);
    },
    remove: function(at, n) { this.removeInner(at - this.first, n); },

    // From here, the methods are part of the public interface. Most
    // are also available from CodeMirror (editor) instances.

    getValue: function(lineSep) {
      var lines = getLines(this, this.first, this.first + this.size);
      if (lineSep === false) return lines;
      return lines.join(lineSep || "\n");
    },
    setValue: docMethodOp(function(code) {
      var top = Pos(this.first, 0), last = this.first + this.size - 1;
      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
                        text: splitLines(code), origin: "setValue"}, true);
      setSelection(this, simpleSelection(top));
    }),
    replaceRange: function(code, from, to, origin) {
      from = clipPos(this, from);
      to = to ? clipPos(this, to) : from;
      replaceRange(this, code, from, to, origin);
    },
    getRange: function(from, to, lineSep) {
      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
      if (lineSep === false) return lines;
      return lines.join(lineSep || "\n");
    },

    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},

    getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
    getLineNumber: function(line) {return lineNo(line);},

    getLineHandleVisualStart: function(line) {
      if (typeof line == "number") line = getLine(this, line);
      return visualLine(line);
    },

    lineCount: function() {return this.size;},
    firstLine: function() {return this.first;},
    lastLine: function() {return this.first + this.size - 1;},

    clipPos: function(pos) {return clipPos(this, pos);},

    getCursor: function(start) {
      var range = this.sel.primary(), pos;
      if (start == null || start == "head") pos = range.head;
      else if (start == "anchor") pos = range.anchor;
      else if (start == "end" || start == "to" || start === false) pos = range.to();
      else pos = range.from();
      return pos;
    },
    listSelections: function() { return this.sel.ranges; },
    somethingSelected: function() {return this.sel.somethingSelected();},

    setCursor: docMethodOp(function(line, ch, options) {
      setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options);
    }),
    setSelection: docMethodOp(function(anchor, head, options) {
      setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);
    }),
    extendSelection: docMethodOp(function(head, other, options) {
      extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
    }),
    extendSelections: docMethodOp(function(heads, options) {
      extendSelections(this, clipPosArray(this, heads, options));
    }),
    extendSelectionsBy: docMethodOp(function(f, options) {
      extendSelections(this, map(this.sel.ranges, f), options);
    }),
    setSelections: docMethodOp(function(ranges, primary, options) {
      if (!ranges.length) return;
      for (var i = 0, out = []; i < ranges.length; i++)
        out[i] = new Range(clipPos(this, ranges[i].anchor),
                           clipPos(this, ranges[i].head));
      if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex);
      setSelection(this, normalizeSelection(out, primary), options);
    }),
    addSelection: docMethodOp(function(anchor, head, options) {
      var ranges = this.sel.ranges.slice(0);
      ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));
      setSelection(this, normalizeSelection(ranges, ranges.length - 1), options);
    }),

    getSelection: function(lineSep) {
      var ranges = this.sel.ranges, lines;
      for (var i = 0; i < ranges.length; i++) {
        var sel = getBetween(this, ranges[i].from(), ranges[i].to());
        lines = lines ? lines.concat(sel) : sel;
      }
      if (lineSep === false) return lines;
      else return lines.join(lineSep || "\n");
    },
    getSelections: function(lineSep) {
      var parts = [], ranges = this.sel.ranges;
      for (var i = 0; i < ranges.length; i++) {
        var sel = getBetween(this, ranges[i].from(), ranges[i].to());
        if (lineSep !== false) sel = sel.join(lineSep || "\n");
        parts[i] = sel;
      }
      return parts;
    },
    replaceSelection: function(code, collapse, origin) {
      var dup = [];
      for (var i = 0; i < this.sel.ranges.length; i++)
        dup[i] = code;
      this.replaceSelections(dup, collapse, origin || "+input");
    },
    replaceSelections: docMethodOp(function(code, collapse, origin) {
      var changes = [], sel = this.sel;
      for (var i = 0; i < sel.ranges.length; i++) {
        var range = sel.ranges[i];
        changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin};
      }
      var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
      for (var i = changes.length - 1; i >= 0; i--)
        makeChange(this, changes[i]);
      if (newSel) setSelectionReplaceHistory(this, newSel);
      else if (this.cm) ensureCursorVisible(this.cm);
    }),
    undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
    redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
    undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
    redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),

    setExtending: function(val) {this.extend = val;},
    getExtending: function() {return this.extend;},

    historySize: function() {
      var hist = this.history, done = 0, undone = 0;
      for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done;
      for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone;
      return {undo: done, redo: undone};
    },
    clearHistory: function() {this.history = new History(this.history.maxGeneration);},

    markClean: function() {
      this.cleanGeneration = this.changeGeneration(true);
    },
    changeGeneration: function(forceSplit) {
      if (forceSplit)
        this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null;
      return this.history.generation;
    },
    isClean: function (gen) {
      return this.history.generation == (gen || this.cleanGeneration);
    },

    getHistory: function() {
      return {done: copyHistoryArray(this.history.done),
              undone: copyHistoryArray(this.history.undone)};
    },
    setHistory: function(histData) {
      var hist = this.history = new History(this.history.maxGeneration);
      hist.done = copyHistoryArray(histData.done.slice(0), null, true);
      hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
    },

    addLineClass: docMethodOp(function(handle, where, cls) {
      return changeLine(this, handle, "class", function(line) {
        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
        if (!line[prop]) line[prop] = cls;
        else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
        else line[prop] += " " + cls;
        return true;
      });
    }),
    removeLineClass: docMethodOp(function(handle, where, cls) {
      return changeLine(this, handle, "class", function(line) {
        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
        var cur = line[prop];
        if (!cur) return false;
        else if (cls == null) line[prop] = null;
        else {
          var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
          if (!found) return false;
          var end = found.index + found[0].length;
          line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
        }
        return true;
      });
    }),

    markText: function(from, to, options) {
      return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
    },
    setBookmark: function(pos, options) {
      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
                      insertLeft: options && options.insertLeft,
                      clearWhenEmpty: false, shared: options && options.shared};
      pos = clipPos(this, pos);
      return markText(this, pos, pos, realOpts, "bookmark");
    },
    findMarksAt: function(pos) {
      pos = clipPos(this, pos);
      var markers = [], spans = getLine(this, pos.line).markedSpans;
      if (spans) for (var i = 0; i < spans.length; ++i) {
        var span = spans[i];
        if ((span.from == null || span.from <= pos.ch) &&
            (span.to == null || span.to >= pos.ch))
          markers.push(span.marker.parent || span.marker);
      }
      return markers;
    },
    findMarks: function(from, to, filter) {
      from = clipPos(this, from); to = clipPos(this, to);
      var found = [], lineNo = from.line;
      this.iter(from.line, to.line + 1, function(line) {
        var spans = line.markedSpans;
        if (spans) for (var i = 0; i < spans.length; i++) {
          var span = spans[i];
          if (!(lineNo == from.line && from.ch > span.to ||
                span.from == null && lineNo != from.line||
                lineNo == to.line && span.from > to.ch) &&
              (!filter || filter(span.marker)))
            found.push(span.marker.parent || span.marker);
        }
        ++lineNo;
      });
      return found;
    },
    getAllMarks: function() {
      var markers = [];
      this.iter(function(line) {
        var sps = line.markedSpans;
        if (sps) for (var i = 0; i < sps.length; ++i)
          if (sps[i].from != null) markers.push(sps[i].marker);
      });
      return markers;
    },

    posFromIndex: function(off) {
      var ch, lineNo = this.first;
      this.iter(function(line) {
        var sz = line.text.length + 1;
        if (sz > off) { ch = off; return true; }
        off -= sz;
        ++lineNo;
      });
      return clipPos(this, Pos(lineNo, ch));
    },
    indexFromPos: function (coords) {
      coords = clipPos(this, coords);
      var index = coords.ch;
      if (coords.line < this.first || coords.ch < 0) return 0;
      this.iter(this.first, coords.line, function (line) {
        index += line.text.length + 1;
      });
      return index;
    },

    copy: function(copyHistory) {
      var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
      doc.sel = this.sel;
      doc.extend = false;
      if (copyHistory) {
        doc.history.undoDepth = this.history.undoDepth;
        doc.setHistory(this.getHistory());
      }
      return doc;
    },

    linkedDoc: function(options) {
      if (!options) options = {};
      var from = this.first, to = this.first + this.size;
      if (options.from != null && options.from > from) from = options.from;
      if (options.to != null && options.to < to) to = options.to;
      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
      if (options.sharedHist) copy.history = this.history;
      (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
      copySharedMarkers(copy, findSharedMarkers(this));
      return copy;
    },
    unlinkDoc: function(other) {
      if (other instanceof CodeMirror) other = other.doc;
      if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
        var link = this.linked[i];
        if (link.doc != other) continue;
        this.linked.splice(i, 1);
        other.unlinkDoc(this);
        detachSharedMarkers(findSharedMarkers(this));
        break;
      }
      // If the histories were shared, split them again
      if (other.history == this.history) {
        var splitIds = [other.id];
        linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
        other.history = new History(null);
        other.history.done = copyHistoryArray(this.history.done, splitIds);
        other.history.undone = copyHistoryArray(this.history.undone, splitIds);
      }
    },
    iterLinkedDocs: function(f) {linkedDocs(this, f);},

    getMode: function() {return this.mode;},
    getEditor: function() {return this.cm;}
  });

  // Public alias.
  Doc.prototype.eachLine = Doc.prototype.iter;

  // Set up methods on CodeMirror's prototype to redirect to the editor's document.
  var dontDelegate = "iter insert remove copy getEditor".split(" ");
  for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
    CodeMirror.prototype[prop] = (function(method) {
      return function() {return method.apply(this.doc, arguments);};
    })(Doc.prototype[prop]);

  eventMixin(Doc);

  // Call f for all linked documents.
  function linkedDocs(doc, f, sharedHistOnly) {
    function propagate(doc, skip, sharedHist) {
      if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
        var rel = doc.linked[i];
        if (rel.doc == skip) continue;
        var shared = sharedHist && rel.sharedHist;
        if (sharedHistOnly && !shared) continue;
        f(rel.doc, shared);
        propagate(rel.doc, doc, shared);
      }
    }
    propagate(doc, null, true);
  }

  // Attach a document to an editor.
  function attachDoc(cm, doc) {
    if (doc.cm) throw new Error("This document is already in use.");
    cm.doc = doc;
    doc.cm = cm;
    estimateLineHeights(cm);
    loadMode(cm);
    if (!cm.options.lineWrapping) findMaxLine(cm);
    cm.options.mode = doc.modeOption;
    regChange(cm);
  }

  // LINE UTILITIES

  // Find the line object corresponding to the given line number.
  function getLine(doc, n) {
    n -= doc.first;
    if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document.");
    for (var chunk = doc; !chunk.lines;) {
      for (var i = 0;; ++i) {
        var child = chunk.children[i], sz = child.chunkSize();
        if (n < sz) { chunk = child; break; }
        n -= sz;
      }
    }
    return chunk.lines[n];
  }

  // Get the part of a document between two positions, as an array of
  // strings.
  function getBetween(doc, start, end) {
    var out = [], n = start.line;
    doc.iter(start.line, end.line + 1, function(line) {
      var text = line.text;
      if (n == end.line) text = text.slice(0, end.ch);
      if (n == start.line) text = text.slice(start.ch);
      out.push(text);
      ++n;
    });
    return out;
  }
  // Get the lines between from and to, as array of strings.
  function getLines(doc, from, to) {
    var out = [];
    doc.iter(from, to, function(line) { out.push(line.text); });
    return out;
  }

  // Update the height of a line, propagating the height change
  // upwards to parent nodes.
  function updateLineHeight(line, height) {
    var diff = height - line.height;
    if (diff) for (var n = line; n; n = n.parent) n.height += diff;
  }

  // Given a line object, find its line number by walking up through
  // its parent links.
  function lineNo(line) {
    if (line.parent == null) return null;
    var cur = line.parent, no = indexOf(cur.lines, line);
    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
      for (var i = 0;; ++i) {
        if (chunk.children[i] == cur) break;
        no += chunk.children[i].chunkSize();
      }
    }
    return no + cur.first;
  }

  // Find the line at the given vertical position, using the height
  // information in the document tree.
  function lineAtHeight(chunk, h) {
    var n = chunk.first;
    outer: do {
      for (var i = 0; i < chunk.children.length; ++i) {
        var child = chunk.children[i], ch = child.height;
        if (h < ch) { chunk = child; continue outer; }
        h -= ch;
        n += child.chunkSize();
      }
      return n;
    } while (!chunk.lines);
    for (var i = 0; i < chunk.lines.length; ++i) {
      var line = chunk.lines[i], lh = line.height;
      if (h < lh) break;
      h -= lh;
    }
    return n + i;
  }


  // Find the height above the given line.
  function heightAtLine(lineObj) {
    lineObj = visualLine(lineObj);

    var h = 0, chunk = lineObj.parent;
    for (var i = 0; i < chunk.lines.length; ++i) {
      var line = chunk.lines[i];
      if (line == lineObj) break;
      else h += line.height;
    }
    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
      for (var i = 0; i < p.children.length; ++i) {
        var cur = p.children[i];
        if (cur == chunk) break;
        else h += cur.height;
      }
    }
    return h;
  }

  // Get the bidi ordering for the given line (and cache it). Returns
  // false for lines that are fully left-to-right, and an array of
  // BidiSpan objects otherwise.
  function getOrder(line) {
    var order = line.order;
    if (order == null) order = line.order = bidiOrdering(line.text);
    return order;
  }

  // HISTORY

  function History(startGen) {
    // Arrays of change events and selections. Doing something adds an
    // event to done and clears undo. Undoing moves events from done
    // to undone, redoing moves them in the other direction.
    this.done = []; this.undone = [];
    this.undoDepth = Infinity;
    // Used to track when changes can be merged into a single undo
    // event
    this.lastModTime = this.lastSelTime = 0;
    this.lastOp = this.lastSelOp = null;
    this.lastOrigin = this.lastSelOrigin = null;
    // Used by the isClean() method
    this.generation = this.maxGeneration = startGen || 1;
  }

  // Create a history change event from an updateDoc-style change
  // object.
  function historyChangeFromChange(doc, change) {
    var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
    linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
    return histChange;
  }

  // Pop all selection events off the end of a history array. Stop at
  // a change event.
  function clearSelectionEvents(array) {
    while (array.length) {
      var last = lst(array);
      if (last.ranges) array.pop();
      else break;
    }
  }

  // Find the top change event in the history. Pop off selection
  // events that are in the way.
  function lastChangeEvent(hist, force) {
    if (force) {
      clearSelectionEvents(hist.done);
      return lst(hist.done);
    } else if (hist.done.length && !lst(hist.done).ranges) {
      return lst(hist.done);
    } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
      hist.done.pop();
      return lst(hist.done);
    }
  }

  // Register a change in the history. Merges changes that are within
  // a single operation, ore are close together with an origin that
  // allows merging (starting with "+") into a single event.
  function addChangeToHistory(doc, change, selAfter, opId) {
    var hist = doc.history;
    hist.undone.length = 0;
    var time = +new Date, cur;

    if ((hist.lastOp == opId ||
         hist.lastOrigin == change.origin && change.origin &&
         ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) ||
          change.origin.charAt(0) == "*")) &&
        (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
      // Merge this change into the last event
      var last = lst(cur.changes);
      if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
        // Optimized case for simple insertion -- don't want to add
        // new changesets for every character typed
        last.to = changeEnd(change);
      } else {
        // Add new sub-event
        cur.changes.push(historyChangeFromChange(doc, change));
      }
    } else {
      // Can not be merged, start a new event.
      var before = lst(hist.done);
      if (!before || !before.ranges)
        pushSelectionToHistory(doc.sel, hist.done);
      cur = {changes: [historyChangeFromChange(doc, change)],
             generation: hist.generation};
      hist.done.push(cur);
      while (hist.done.length > hist.undoDepth) {
        hist.done.shift();
        if (!hist.done[0].ranges) hist.done.shift();
      }
    }
    hist.done.push(selAfter);
    hist.generation = ++hist.maxGeneration;
    hist.lastModTime = hist.lastSelTime = time;
    hist.lastOp = hist.lastSelOp = opId;
    hist.lastOrigin = hist.lastSelOrigin = change.origin;

    if (!last) signal(doc, "historyAdded");
  }

  function selectionEventCanBeMerged(doc, origin, prev, sel) {
    var ch = origin.charAt(0);
    return ch == "*" ||
      ch == "+" &&
      prev.ranges.length == sel.ranges.length &&
      prev.somethingSelected() == sel.somethingSelected() &&
      new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500);
  }

  // Called whenever the selection changes, sets the new selection as
  // the pending selection in the history, and pushes the old pending
  // selection into the 'done' array when it was significantly
  // different (in number of selected ranges, emptiness, or time).
  function addSelectionToHistory(doc, sel, opId, options) {
    var hist = doc.history, origin = options && options.origin;

    // A new event is started when the previous origin does not match
    // the current, or the origins don't allow matching. Origins
    // starting with * are always merged, those starting with + are
    // merged when similar and close together in time.
    if (opId == hist.lastSelOp ||
        (origin && hist.lastSelOrigin == origin &&
         (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
          selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
      hist.done[hist.done.length - 1] = sel;
    else
      pushSelectionToHistory(sel, hist.done);

    hist.lastSelTime = +new Date;
    hist.lastSelOrigin = origin;
    hist.lastSelOp = opId;
    if (options && options.clearRedo !== false)
      clearSelectionEvents(hist.undone);
  }

  function pushSelectionToHistory(sel, dest) {
    var top = lst(dest);
    if (!(top && top.ranges && top.equals(sel)))
      dest.push(sel);
  }

  // Used to store marked span information in the history.
  function attachLocalSpans(doc, change, from, to) {
    var existing = change["spans_" + doc.id], n = 0;
    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
      if (line.markedSpans)
        (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
      ++n;
    });
  }

  // When un/re-doing restores text containing marked spans, those
  // that have been explicitly cleared should not be restored.
  function removeClearedSpans(spans) {
    if (!spans) return null;
    for (var i = 0, out; i < spans.length; ++i) {
      if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
      else if (out) out.push(spans[i]);
    }
    return !out ? spans : out.length ? out : null;
  }

  // Retrieve and filter the old marked spans stored in a change event.
  function getOldSpans(doc, change) {
    var found = change["spans_" + doc.id];
    if (!found) return null;
    for (var i = 0, nw = []; i < change.text.length; ++i)
      nw.push(removeClearedSpans(found[i]));
    return nw;
  }

  // Used both to provide a JSON-safe object in .getHistory, and, when
  // detaching a document, to split the history in two
  function copyHistoryArray(events, newGroup, instantiateSel) {
    for (var i = 0, copy = []; i < events.length; ++i) {
      var event = events[i];
      if (event.ranges) {
        copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);
        continue;
      }
      var changes = event.changes, newChanges = [];
      copy.push({changes: newChanges});
      for (var j = 0; j < changes.length; ++j) {
        var change = changes[j], m;
        newChanges.push({from: change.from, to: change.to, text: change.text});
        if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
          if (indexOf(newGroup, Number(m[1])) > -1) {
            lst(newChanges)[prop] = change[prop];
            delete change[prop];
          }
        }
      }
    }
    return copy;
  }

  // Rebasing/resetting history to deal with externally-sourced changes

  function rebaseHistSelSingle(pos, from, to, diff) {
    if (to < pos.line) {
      pos.line += diff;
    } else if (from < pos.line) {
      pos.line = from;
      pos.ch = 0;
    }
  }

  // Tries to rebase an array of history events given a change in the
  // document. If the change touches the same lines as the event, the
  // event, and everything 'behind' it, is discarded. If the change is
  // before the event, the event's positions are updated. Uses a
  // copy-on-write scheme for the positions, to avoid having to
  // reallocate them all on every rebase, but also avoid problems with
  // shared position objects being unsafely updated.
  function rebaseHistArray(array, from, to, diff) {
    for (var i = 0; i < array.length; ++i) {
      var sub = array[i], ok = true;
      if (sub.ranges) {
        if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }
        for (var j = 0; j < sub.ranges.length; j++) {
          rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);
          rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);
        }
        continue;
      }
      for (var j = 0; j < sub.changes.length; ++j) {
        var cur = sub.changes[j];
        if (to < cur.from.line) {
          cur.from = Pos(cur.from.line + diff, cur.from.ch);
          cur.to = Pos(cur.to.line + diff, cur.to.ch);
        } else if (from <= cur.to.line) {
          ok = false;
          break;
        }
      }
      if (!ok) {
        array.splice(0, i + 1);
        i = 0;
      }
    }
  }

  function rebaseHist(hist, change) {
    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
    rebaseHistArray(hist.done, from, to, diff);
    rebaseHistArray(hist.undone, from, to, diff);
  }

  // EVENT UTILITIES

  // Due to the fact that we still support jurassic IE versions, some
  // compatibility wrappers are needed.

  var e_preventDefault = CodeMirror.e_preventDefault = function(e) {
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;
  };
  var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) {
    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
  };
  function e_defaultPrevented(e) {
    return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
  }
  var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);};

  function e_target(e) {return e.target || e.srcElement;}
  function e_button(e) {
    var b = e.which;
    if (b == null) {
      if (e.button & 1) b = 1;
      else if (e.button & 2) b = 3;
      else if (e.button & 4) b = 2;
    }
    if (mac && e.ctrlKey && b == 1) b = 3;
    return b;
  }

  // EVENT HANDLING

  // Lightweight event framework. on/off also work on DOM nodes,
  // registering native DOM handlers.

  var on = CodeMirror.on = function(emitter, type, f) {
    if (emitter.addEventListener)
      emitter.addEventListener(type, f, false);
    else if (emitter.attachEvent)
      emitter.attachEvent("on" + type, f);
    else {
      var map = emitter._handlers || (emitter._handlers = {});
      var arr = map[type] || (map[type] = []);
      arr.push(f);
    }
  };

  var off = CodeMirror.off = function(emitter, type, f) {
    if (emitter.removeEventListener)
      emitter.removeEventListener(type, f, false);
    else if (emitter.detachEvent)
      emitter.detachEvent("on" + type, f);
    else {
      var arr = emitter._handlers && emitter._handlers[type];
      if (!arr) return;
      for (var i = 0; i < arr.length; ++i)
        if (arr[i] == f) { arr.splice(i, 1); break; }
    }
  };

  var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
    var arr = emitter._handlers && emitter._handlers[type];
    if (!arr) return;
    var args = Array.prototype.slice.call(arguments, 2);
    for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
  };

  var orphanDelayedCallbacks = null;

  // Often, we want to signal events at a point where we are in the
  // middle of some work, but don't want the handler to start calling
  // other methods on the editor, which might be in an inconsistent
  // state or simply not expect any other events to happen.
  // signalLater looks whether there are any handlers, and schedules
  // them to be executed when the last operation ends, or, if no
  // operation is active, when a timeout fires.
  function signalLater(emitter, type /*, values...*/) {
    var arr = emitter._handlers && emitter._handlers[type];
    if (!arr) return;
    var args = Array.prototype.slice.call(arguments, 2), list;
    if (operationGroup) {
      list = operationGroup.delayedCallbacks;
    } else if (orphanDelayedCallbacks) {
      list = orphanDelayedCallbacks;
    } else {
      list = orphanDelayedCallbacks = [];
      setTimeout(fireOrphanDelayed, 0);
    }
    function bnd(f) {return function(){f.apply(null, args);};};
    for (var i = 0; i < arr.length; ++i)
      list.push(bnd(arr[i]));
  }

  function fireOrphanDelayed() {
    var delayed = orphanDelayedCallbacks;
    orphanDelayedCallbacks = null;
    for (var i = 0; i < delayed.length; ++i) delayed[i]();
  }

  // The DOM events that CodeMirror handles can be overridden by
  // registering a (non-DOM) handler on the editor for the event name,
  // and preventDefault-ing the event in that handler.
  function signalDOMEvent(cm, e, override) {
    signal(cm, override || e.type, cm, e);
    return e_defaultPrevented(e) || e.codemirrorIgnore;
  }

  function signalCursorActivity(cm) {
    var arr = cm._handlers && cm._handlers.cursorActivity;
    if (!arr) return;
    var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
    for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1)
      set.push(arr[i]);
  }

  function hasHandler(emitter, type) {
    var arr = emitter._handlers && emitter._handlers[type];
    return arr && arr.length > 0;
  }

  // Add on and off methods to a constructor's prototype, to make
  // registering events on such objects more convenient.
  function eventMixin(ctor) {
    ctor.prototype.on = function(type, f) {on(this, type, f);};
    ctor.prototype.off = function(type, f) {off(this, type, f);};
  }

  // MISC UTILITIES

  // Number of pixels added to scroller and sizer to hide scrollbar
  var scrollerCutOff = 30;

  // Returned or thrown by various protocols to signal 'I'm not
  // handling this'.
  var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};

  // Reused option objects for setSelection & friends
  var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};

  function Delayed() {this.id = null;}
  Delayed.prototype.set = function(ms, f) {
    clearTimeout(this.id);
    this.id = setTimeout(f, ms);
  };

  // Counts the column offset in a string, taking tabs into account.
  // Used mostly to find indentation.
  var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) {
    if (end == null) {
      end = string.search(/[^\s\u00a0]/);
      if (end == -1) end = string.length;
    }
    for (var i = startIndex || 0, n = startValue || 0;;) {
      var nextTab = string.indexOf("\t", i);
      if (nextTab < 0 || nextTab >= end)
        return n + (end - i);
      n += nextTab - i;
      n += tabSize - (n % tabSize);
      i = nextTab + 1;
    }
  };

  // The inverse of countColumn -- find the offset that corresponds to
  // a particular column.
  function findColumn(string, goal, tabSize) {
    for (var pos = 0, col = 0;;) {
      var nextTab = string.indexOf("\t", pos);
      if (nextTab == -1) nextTab = string.length;
      var skipped = nextTab - pos;
      if (nextTab == string.length || col + skipped >= goal)
        return pos + Math.min(skipped, goal - col);
      col += nextTab - pos;
      col += tabSize - (col % tabSize);
      pos = nextTab + 1;
      if (col >= goal) return pos;
    }
  }

  var spaceStrs = [""];
  function spaceStr(n) {
    while (spaceStrs.length <= n)
      spaceStrs.push(lst(spaceStrs) + " ");
    return spaceStrs[n];
  }

  function lst(arr) { return arr[arr.length-1]; }

  var selectInput = function(node) { node.select(); };
  if (ios) // Mobile Safari apparently has a bug where select() is broken.
    selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; };
  else if (ie) // Suppress mysterious IE10 errors
    selectInput = function(node) { try { node.select(); } catch(_e) {} };

  function indexOf(array, elt) {
    for (var i = 0; i < array.length; ++i)
      if (array[i] == elt) return i;
    return -1;
  }
  if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); };
  function map(array, f) {
    var out = [];
    for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
    return out;
  }
  if ([].map) map = function(array, f) { return array.map(f); };

  function createObj(base, props) {
    var inst;
    if (Object.create) {
      inst = Object.create(base);
    } else {
      var ctor = function() {};
      ctor.prototype = base;
      inst = new ctor();
    }
    if (props) copyObj(props, inst);
    return inst;
  };

  function copyObj(obj, target, overwrite) {
    if (!target) target = {};
    for (var prop in obj)
      if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
        target[prop] = obj[prop];
    return target;
  }

  function bind(f) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function(){return f.apply(null, args);};
  }

  var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
  var isWordCharBasic = CodeMirror.isWordChar = function(ch) {
    return /\w/.test(ch) || ch > "\x80" &&
      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
  };
  function isWordChar(ch, helper) {
    if (!helper) return isWordCharBasic(ch);
    if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true;
    return helper.test(ch);
  }

  function isEmpty(obj) {
    for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
    return true;
  }

  // Extending unicode characters. A series of a non-extending char +
  // any number of extending chars is treated as a single unit as far
  // as editing and measuring is concerned. This is not fully correct,
  // since some scripts/fonts/browsers also treat other configurations
  // of code points as a group.
  var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
  function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); }

  // DOM UTILITIES

  function elt(tag, content, className, style) {
    var e = document.createElement(tag);
    if (className) e.className = className;
    if (style) e.style.cssText = style;
    if (typeof content == "string") e.appendChild(document.createTextNode(content));
    else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
    return e;
  }

  var range;
  if (document.createRange) range = function(node, start, end) {
    var r = document.createRange();
    r.setEnd(node, end);
    r.setStart(node, start);
    return r;
  };
  else range = function(node, start, end) {
    var r = document.body.createTextRange();
    r.moveToElementText(node.parentNode);
    r.collapse(true);
    r.moveEnd("character", end);
    r.moveStart("character", start);
    return r;
  };

  function removeChildren(e) {
    for (var count = e.childNodes.length; count > 0; --count)
      e.removeChild(e.firstChild);
    return e;
  }

  function removeChildrenAndAdd(parent, e) {
    return removeChildren(parent).appendChild(e);
  }

  function contains(parent, child) {
    if (parent.contains)
      return parent.contains(child);
    while (child = child.parentNode)
      if (child == parent) return true;
  }

  function activeElt() { return document.activeElement; }
  // Older versions of IE throws unspecified error when touching
  // document.activeElement in some cases (during loading, in iframe)
  if (ie && ie_version < 11) activeElt = function() {
    try { return document.activeElement; }
    catch(e) { return document.body; }
  };

  function classTest(cls) { return new RegExp("\\b" + cls + "\\b\\s*"); }
  function rmClass(node, cls) {
    var test = classTest(cls);
    if (test.test(node.className)) node.className = node.className.replace(test, "");
  }
  function addClass(node, cls) {
    if (!classTest(cls).test(node.className)) node.className += " " + cls;
  }
  function joinClasses(a, b) {
    var as = a.split(" ");
    for (var i = 0; i < as.length; i++)
      if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i];
    return b;
  }

  // WINDOW-WIDE EVENTS

  // These must be handled carefully, because naively registering a
  // handler for each editor will cause the editors to never be
  // garbage collected.

  function forEachCodeMirror(f) {
    if (!document.body.getElementsByClassName) return;
    var byClass = document.body.getElementsByClassName("CodeMirror");
    for (var i = 0; i < byClass.length; i++) {
      var cm = byClass[i].CodeMirror;
      if (cm) f(cm);
    }
  }

  var globalsRegistered = false;
  function ensureGlobalHandlers() {
    if (globalsRegistered) return;
    registerGlobalHandlers();
    globalsRegistered = true;
  }
  function registerGlobalHandlers() {
    // When the window resizes, we need to refresh active editors.
    var resizeTimer;
    on(window, "resize", function() {
      if (resizeTimer == null) resizeTimer = setTimeout(function() {
        resizeTimer = null;
        knownScrollbarWidth = null;
        forEachCodeMirror(onResize);
      }, 100);
    });
    // When the window loses focus, we want to show the editor as blurred
    on(window, "blur", function() {
      forEachCodeMirror(onBlur);
    });
  }

  // FEATURE DETECTION

  // Detect drag-and-drop
  var dragAndDrop = function() {
    // There is *some* kind of drag-and-drop support in IE6-8, but I
    // couldn't get it to work yet.
    if (ie && ie_version < 9) return false;
    var div = elt('div');
    return "draggable" in div || "dragDrop" in div;
  }();

  var knownScrollbarWidth;
  function scrollbarWidth(measure) {
    if (knownScrollbarWidth != null) return knownScrollbarWidth;
    var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
    removeChildrenAndAdd(measure, test);
    if (test.offsetWidth)
      knownScrollbarWidth = test.offsetHeight - test.clientHeight;
    return knownScrollbarWidth || 0;
  }

  var zwspSupported;
  function zeroWidthElement(measure) {
    if (zwspSupported == null) {
      var test = elt("span", "\u200b");
      removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
      if (measure.firstChild.offsetHeight != 0)
        zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8);
    }
    if (zwspSupported) return elt("span", "\u200b");
    else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
  }

  // Feature-detect IE's crummy client rect reporting for bidi text
  var badBidiRects;
  function hasBadBidiRects(measure) {
    if (badBidiRects != null) return badBidiRects;
    var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
    var r0 = range(txt, 0, 1).getBoundingClientRect();
    if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780)
    var r1 = range(txt, 1, 2).getBoundingClientRect();
    return badBidiRects = (r1.right - r0.right < 3);
  }

  // See if "".split is the broken IE version, if so, provide an
  // alternative way to split lines.
  var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
    var pos = 0, result = [], l = string.length;
    while (pos <= l) {
      var nl = string.indexOf("\n", pos);
      if (nl == -1) nl = string.length;
      var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
      var rt = line.indexOf("\r");
      if (rt != -1) {
        result.push(line.slice(0, rt));
        pos += rt + 1;
      } else {
        result.push(line);
        pos = nl + 1;
      }
    }
    return result;
  } : function(string){return string.split(/\r\n?|\n/);};

  var hasSelection = window.getSelection ? function(te) {
    try { return te.selectionStart != te.selectionEnd; }
    catch(e) { return false; }
  } : function(te) {
    try {var range = te.ownerDocument.selection.createRange();}
    catch(e) {}
    if (!range || range.parentElement() != te) return false;
    return range.compareEndPoints("StartToEnd", range) != 0;
  };

  var hasCopyEvent = (function() {
    var e = elt("div");
    if ("oncopy" in e) return true;
    e.setAttribute("oncopy", "return;");
    return typeof e.oncopy == "function";
  })();

  var badZoomedRects = null;
  function hasBadZoomedRects(measure) {
    if (badZoomedRects != null) return badZoomedRects;
    var node = removeChildrenAndAdd(measure, elt("span", "x"));
    var normal = node.getBoundingClientRect();
    var fromRange = range(node, 0, 1).getBoundingClientRect();
    return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1;
  }

  // KEY NAMES

  var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
                  19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
                  36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
                  46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
                  173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
                  221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
                  63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
  CodeMirror.keyNames = keyNames;
  (function() {
    // Number keys
    for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
    // Alphabetic keys
    for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
    // Function keys
    for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
  })();

  // BIDI HELPERS

  function iterateBidiSections(order, from, to, f) {
    if (!order) return f(from, to, "ltr");
    var found = false;
    for (var i = 0; i < order.length; ++i) {
      var part = order[i];
      if (part.from < to && part.to > from || from == to && part.to == from) {
        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
        found = true;
      }
    }
    if (!found) f(from, to, "ltr");
  }

  function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
  function bidiRight(part) { return part.level % 2 ? part.from : part.to; }

  function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
  function lineRight(line) {
    var order = getOrder(line);
    if (!order) return line.text.length;
    return bidiRight(lst(order));
  }

  function lineStart(cm, lineN) {
    var line = getLine(cm.doc, lineN);
    var visual = visualLine(line);
    if (visual != line) lineN = lineNo(visual);
    var order = getOrder(visual);
    var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
    return Pos(lineN, ch);
  }
  function lineEnd(cm, lineN) {
    var merged, line = getLine(cm.doc, lineN);
    while (merged = collapsedSpanAtEnd(line)) {
      line = merged.find(1, true).line;
      lineN = null;
    }
    var order = getOrder(line);
    var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
    return Pos(lineN == null ? lineNo(line) : lineN, ch);
  }
  function lineStartSmart(cm, pos) {
    var start = lineStart(cm, pos.line);
    var line = getLine(cm.doc, start.line);
    var order = getOrder(line);
    if (!order || order[0].level == 0) {
      var firstNonWS = Math.max(0, line.text.search(/\S/));
      var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
      return Pos(start.line, inWS ? 0 : firstNonWS);
    }
    return start;
  }

  function compareBidiLevel(order, a, b) {
    var linedir = order[0].level;
    if (a == linedir) return true;
    if (b == linedir) return false;
    return a < b;
  }
  var bidiOther;
  function getBidiPartAt(order, pos) {
    bidiOther = null;
    for (var i = 0, found; i < order.length; ++i) {
      var cur = order[i];
      if (cur.from < pos && cur.to > pos) return i;
      if ((cur.from == pos || cur.to == pos)) {
        if (found == null) {
          found = i;
        } else if (compareBidiLevel(order, cur.level, order[found].level)) {
          if (cur.from != cur.to) bidiOther = found;
          return i;
        } else {
          if (cur.from != cur.to) bidiOther = i;
          return found;
        }
      }
    }
    return found;
  }

  function moveInLine(line, pos, dir, byUnit) {
    if (!byUnit) return pos + dir;
    do pos += dir;
    while (pos > 0 && isExtendingChar(line.text.charAt(pos)));
    return pos;
  }

  // This is needed in order to move 'visually' through bi-directional
  // text -- i.e., pressing left should make the cursor go left, even
  // when in RTL text. The tricky part is the 'jumps', where RTL and
  // LTR text touch each other. This often requires the cursor offset
  // to move more than one unit, in order to visually move one unit.
  function moveVisually(line, start, dir, byUnit) {
    var bidi = getOrder(line);
    if (!bidi) return moveLogically(line, start, dir, byUnit);
    var pos = getBidiPartAt(bidi, start), part = bidi[pos];
    var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);

    for (;;) {
      if (target > part.from && target < part.to) return target;
      if (target == part.from || target == part.to) {
        if (getBidiPartAt(bidi, target) == pos) return target;
        part = bidi[pos += dir];
        return (dir > 0) == part.level % 2 ? part.to : part.from;
      } else {
        part = bidi[pos += dir];
        if (!part) return null;
        if ((dir > 0) == part.level % 2)
          target = moveInLine(line, part.to, -1, byUnit);
        else
          target = moveInLine(line, part.from, 1, byUnit);
      }
    }
  }

  function moveLogically(line, start, dir, byUnit) {
    var target = start + dir;
    if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir;
    return target < 0 || target > line.text.length ? null : target;
  }

  // Bidirectional ordering algorithm
  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
  // that this (partially) implements.

  // One-char codes used for character types:
  // L (L):   Left-to-Right
  // R (R):   Right-to-Left
  // r (AL):  Right-to-Left Arabic
  // 1 (EN):  European Number
  // + (ES):  European Number Separator
  // % (ET):  European Number Terminator
  // n (AN):  Arabic Number
  // , (CS):  Common Number Separator
  // m (NSM): Non-Spacing Mark
  // b (BN):  Boundary Neutral
  // s (B):   Paragraph Separator
  // t (S):   Segment Separator
  // w (WS):  Whitespace
  // N (ON):  Other Neutrals

  // Returns null if characters are ordered as they appear
  // (left-to-right), or an array of sections ({from, to, level}
  // objects) in the order in which they occur visually.
  var bidiOrdering = (function() {
    // Character types for codepoints 0 to 0xff
    var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
    // Character types for codepoints 0x600 to 0x6ff
    var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";
    function charType(code) {
      if (code <= 0xf7) return lowTypes.charAt(code);
      else if (0x590 <= code && code <= 0x5f4) return "R";
      else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600);
      else if (0x6ee <= code && code <= 0x8ac) return "r";
      else if (0x2000 <= code && code <= 0x200b) return "w";
      else if (code == 0x200c) return "b";
      else return "L";
    }

    var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
    // Browsers seem to always treat the boundaries of block elements as being L.
    var outerType = "L";

    function BidiSpan(level, from, to) {
      this.level = level;
      this.from = from; this.to = to;
    }

    return function(str) {
      if (!bidiRE.test(str)) return false;
      var len = str.length, types = [];
      for (var i = 0, type; i < len; ++i)
        types.push(type = charType(str.charCodeAt(i)));

      // W1. Examine each non-spacing mark (NSM) in the level run, and
      // change the type of the NSM to the type of the previous
      // character. If the NSM is at the start of the level run, it will
      // get the type of sor.
      for (var i = 0, prev = outerType; i < len; ++i) {
        var type = types[i];
        if (type == "m") types[i] = prev;
        else prev = type;
      }

      // W2. Search backwards from each instance of a European number
      // until the first strong type (R, L, AL, or sor) is found. If an
      // AL is found, change the type of the European number to Arabic
      // number.
      // W3. Change all ALs to R.
      for (var i = 0, cur = outerType; i < len; ++i) {
        var type = types[i];
        if (type == "1" && cur == "r") types[i] = "n";
        else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
      }

      // W4. A single European separator between two European numbers
      // changes to a European number. A single common separator between
      // two numbers of the same type changes to that type.
      for (var i = 1, prev = types[0]; i < len - 1; ++i) {
        var type = types[i];
        if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
        else if (type == "," && prev == types[i+1] &&
                 (prev == "1" || prev == "n")) types[i] = prev;
        prev = type;
      }

      // W5. A sequence of European terminators adjacent to European
      // numbers changes to all European numbers.
      // W6. Otherwise, separators and terminators change to Other
      // Neutral.
      for (var i = 0; i < len; ++i) {
        var type = types[i];
        if (type == ",") types[i] = "N";
        else if (type == "%") {
          for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
          var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
          for (var j = i; j < end; ++j) types[j] = replace;
          i = end - 1;
        }
      }

      // W7. Search backwards from each instance of a European number
      // until the first strong type (R, L, or sor) is found. If an L is
      // found, then change the type of the European number to L.
      for (var i = 0, cur = outerType; i < len; ++i) {
        var type = types[i];
        if (cur == "L" && type == "1") types[i] = "L";
        else if (isStrong.test(type)) cur = type;
      }

      // N1. A sequence of neutrals takes the direction of the
      // surrounding strong text if the text on both sides has the same
      // direction. European and Arabic numbers act as if they were R in
      // terms of their influence on neutrals. Start-of-level-run (sor)
      // and end-of-level-run (eor) are used at level run boundaries.
      // N2. Any remaining neutrals take the embedding direction.
      for (var i = 0; i < len; ++i) {
        if (isNeutral.test(types[i])) {
          for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
          var before = (i ? types[i-1] : outerType) == "L";
          var after = (end < len ? types[end] : outerType) == "L";
          var replace = before || after ? "L" : "R";
          for (var j = i; j < end; ++j) types[j] = replace;
          i = end - 1;
        }
      }

      // Here we depart from the documented algorithm, in order to avoid
      // building up an actual levels array. Since there are only three
      // levels (0, 1, 2) in an implementation that doesn't take
      // explicit embedding into account, we can build up the order on
      // the fly, without following the level-based algorithm.
      var order = [], m;
      for (var i = 0; i < len;) {
        if (countsAsLeft.test(types[i])) {
          var start = i;
          for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
          order.push(new BidiSpan(0, start, i));
        } else {
          var pos = i, at = order.length;
          for (++i; i < len && types[i] != "L"; ++i) {}
          for (var j = pos; j < i;) {
            if (countsAsNum.test(types[j])) {
              if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j));
              var nstart = j;
              for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
              order.splice(at, 0, new BidiSpan(2, nstart, j));
              pos = j;
            } else ++j;
          }
          if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i));
        }
      }
      if (order[0].level == 1 && (m = str.match(/^\s+/))) {
        order[0].from = m[0].length;
        order.unshift(new BidiSpan(0, 0, m[0].length));
      }
      if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
        lst(order).to -= m[0].length;
        order.push(new BidiSpan(0, len - m[0].length, len));
      }
      if (order[0].level != lst(order).level)
        order.push(new BidiSpan(order[0].level, len, len));

      return order;
    };
  })();

  // THE END

  CodeMirror.version = "4.7.0";

  return CodeMirror;
});

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("xml", function(config, parserConfig) {
  var indentUnit = config.indentUnit;
  var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;
  var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag;
  if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true;

  var Kludges = parserConfig.htmlMode ? {
    autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
                      'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
                      'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
                      'track': true, 'wbr': true, 'menuitem': true},
    implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
                       'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
                       'th': true, 'tr': true},
    contextGrabbers: {
      'dd': {'dd': true, 'dt': true},
      'dt': {'dd': true, 'dt': true},
      'li': {'li': true},
      'option': {'option': true, 'optgroup': true},
      'optgroup': {'optgroup': true},
      'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
            'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
            'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
            'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
            'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
      'rp': {'rp': true, 'rt': true},
      'rt': {'rp': true, 'rt': true},
      'tbody': {'tbody': true, 'tfoot': true},
      'td': {'td': true, 'th': true},
      'tfoot': {'tbody': true},
      'th': {'td': true, 'th': true},
      'thead': {'tbody': true, 'tfoot': true},
      'tr': {'tr': true}
    },
    doNotIndent: {"pre": true},
    allowUnquoted: true,
    allowMissing: true,
    caseFold: true
  } : {
    autoSelfClosers: {},
    implicitlyClosed: {},
    contextGrabbers: {},
    doNotIndent: {},
    allowUnquoted: false,
    allowMissing: false,
    caseFold: false
  };
  var alignCDATA = parserConfig.alignCDATA;

  // Return variables for tokenizers
  var type, setStyle;

  function inText(stream, state) {
    function chain(parser) {
      state.tokenize = parser;
      return parser(stream, state);
    }

    var ch = stream.next();
    if (ch == "<") {
      if (stream.eat("!")) {
        if (stream.eat("[")) {
          if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
          else return null;
        } else if (stream.match("--")) {
          return chain(inBlock("comment", "-->"));
        } else if (stream.match("DOCTYPE", true, true)) {
          stream.eatWhile(/[\w\._\-]/);
          return chain(doctype(1));
        } else {
          return null;
        }
      } else if (stream.eat("?")) {
        stream.eatWhile(/[\w\._\-]/);
        state.tokenize = inBlock("meta", "?>");
        return "meta";
      } else {
        type = stream.eat("/") ? "closeTag" : "openTag";
        state.tokenize = inTag;
        return "tag bracket";
      }
    } else if (ch == "&") {
      var ok;
      if (stream.eat("#")) {
        if (stream.eat("x")) {
          ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
        } else {
          ok = stream.eatWhile(/[\d]/) && stream.eat(";");
        }
      } else {
        ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
      }
      return ok ? "atom" : "error";
    } else {
      stream.eatWhile(/[^&<]/);
      return null;
    }
  }

  function inTag(stream, state) {
    var ch = stream.next();
    if (ch == ">" || (ch == "/" && stream.eat(">"))) {
      state.tokenize = inText;
      type = ch == ">" ? "endTag" : "selfcloseTag";
      return "tag bracket";
    } else if (ch == "=") {
      type = "equals";
      return null;
    } else if (ch == "<") {
      state.tokenize = inText;
      state.state = baseState;
      state.tagName = state.tagStart = null;
      var next = state.tokenize(stream, state);
      return next ? next + " tag error" : "tag error";
    } else if (/[\'\"]/.test(ch)) {
      state.tokenize = inAttribute(ch);
      state.stringStartCol = stream.column();
      return state.tokenize(stream, state);
    } else {
      stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
      return "word";
    }
  }

  function inAttribute(quote) {
    var closure = function(stream, state) {
      while (!stream.eol()) {
        if (stream.next() == quote) {
          state.tokenize = inTag;
          break;
        }
      }
      return "string";
    };
    closure.isInAttribute = true;
    return closure;
  }

  function inBlock(style, terminator) {
    return function(stream, state) {
      while (!stream.eol()) {
        if (stream.match(terminator)) {
          state.tokenize = inText;
          break;
        }
        stream.next();
      }
      return style;
    };
  }
  function doctype(depth) {
    return function(stream, state) {
      var ch;
      while ((ch = stream.next()) != null) {
        if (ch == "<") {
          state.tokenize = doctype(depth + 1);
          return state.tokenize(stream, state);
        } else if (ch == ">") {
          if (depth == 1) {
            state.tokenize = inText;
            break;
          } else {
            state.tokenize = doctype(depth - 1);
            return state.tokenize(stream, state);
          }
        }
      }
      return "meta";
    };
  }

  function Context(state, tagName, startOfLine) {
    this.prev = state.context;
    this.tagName = tagName;
    this.indent = state.indented;
    this.startOfLine = startOfLine;
    if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
      this.noIndent = true;
  }
  function popContext(state) {
    if (state.context) state.context = state.context.prev;
  }
  function maybePopContext(state, nextTagName) {
    var parentTagName;
    while (true) {
      if (!state.context) {
        return;
      }
      parentTagName = state.context.tagName;
      if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
          !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
        return;
      }
      popContext(state);
    }
  }

  function baseState(type, stream, state) {
    if (type == "openTag") {
      state.tagStart = stream.column();
      return tagNameState;
    } else if (type == "closeTag") {
      return closeTagNameState;
    } else {
      return baseState;
    }
  }
  function tagNameState(type, stream, state) {
    if (type == "word") {
      state.tagName = stream.current();
      setStyle = "tag";
      return attrState;
    } else {
      setStyle = "error";
      return tagNameState;
    }
  }
  function closeTagNameState(type, stream, state) {
    if (type == "word") {
      var tagName = stream.current();
      if (state.context && state.context.tagName != tagName &&
          Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName))
        popContext(state);
      if (state.context && state.context.tagName == tagName) {
        setStyle = "tag";
        return closeState;
      } else {
        setStyle = "tag error";
        return closeStateErr;
      }
    } else {
      setStyle = "error";
      return closeStateErr;
    }
  }

  function closeState(type, _stream, state) {
    if (type != "endTag") {
      setStyle = "error";
      return closeState;
    }
    popContext(state);
    return baseState;
  }
  function closeStateErr(type, stream, state) {
    setStyle = "error";
    return closeState(type, stream, state);
  }

  function attrState(type, _stream, state) {
    if (type == "word") {
      setStyle = "attribute";
      return attrEqState;
    } else if (type == "endTag" || type == "selfcloseTag") {
      var tagName = state.tagName, tagStart = state.tagStart;
      state.tagName = state.tagStart = null;
      if (type == "selfcloseTag" ||
          Kludges.autoSelfClosers.hasOwnProperty(tagName)) {
        maybePopContext(state, tagName);
      } else {
        maybePopContext(state, tagName);
        state.context = new Context(state, tagName, tagStart == state.indented);
      }
      return baseState;
    }
    setStyle = "error";
    return attrState;
  }
  function attrEqState(type, stream, state) {
    if (type == "equals") return attrValueState;
    if (!Kludges.allowMissing) setStyle = "error";
    return attrState(type, stream, state);
  }
  function attrValueState(type, stream, state) {
    if (type == "string") return attrContinuedState;
    if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;}
    setStyle = "error";
    return attrState(type, stream, state);
  }
  function attrContinuedState(type, stream, state) {
    if (type == "string") return attrContinuedState;
    return attrState(type, stream, state);
  }

  return {
    startState: function() {
      return {tokenize: inText,
              state: baseState,
              indented: 0,
              tagName: null, tagStart: null,
              context: null};
    },

    token: function(stream, state) {
      if (!state.tagName && stream.sol())
        state.indented = stream.indentation();

      if (stream.eatSpace()) return null;
      type = null;
      var style = state.tokenize(stream, state);
      if ((style || type) && style != "comment") {
        setStyle = null;
        state.state = state.state(type || style, stream, state);
        if (setStyle)
          style = setStyle == "error" ? style + " error" : setStyle;
      }
      return style;
    },

    indent: function(state, textAfter, fullLine) {
      var context = state.context;
      // Indent multi-line strings (e.g. css).
      if (state.tokenize.isInAttribute) {
        if (state.tagStart == state.indented)
          return state.stringStartCol + 1;
        else
          return state.indented + indentUnit;
      }
      if (context && context.noIndent) return CodeMirror.Pass;
      if (state.tokenize != inTag && state.tokenize != inText)
        return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
      // Indent the starts of attribute names.
      if (state.tagName) {
        if (multilineTagIndentPastTag)
          return state.tagStart + state.tagName.length + 2;
        else
          return state.tagStart + indentUnit * multilineTagIndentFactor;
      }
      if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
      var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
      if (tagAfter && tagAfter[1]) { // Closing tag spotted
        while (context) {
          if (context.tagName == tagAfter[2]) {
            context = context.prev;
            break;
          } else if (Kludges.implicitlyClosed.hasOwnProperty(context.tagName)) {
            context = context.prev;
          } else {
            break;
          }
        }
      } else if (tagAfter) { // Opening tag spotted
        while (context) {
          var grabbers = Kludges.contextGrabbers[context.tagName];
          if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
            context = context.prev;
          else
            break;
        }
      }
      while (context && !context.startOfLine)
        context = context.prev;
      if (context) return context.indent + indentUnit;
      else return 0;
    },

    electricInput: /<\/[\s\w:]+>$/,
    blockCommentStart: "<!--",
    blockCommentEnd: "-->",

    configuration: parserConfig.htmlMode ? "html" : "xml",
    helperType: parserConfig.htmlMode ? "html" : "xml"
  };
});

CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
  CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});

});

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

// TODO actually recognize syntax of TypeScript constructs

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("javascript", function(config, parserConfig) {
  var indentUnit = config.indentUnit;
  var statementIndent = parserConfig.statementIndent;
  var jsonldMode = parserConfig.jsonld;
  var jsonMode = parserConfig.json || jsonldMode;
  var isTS = parserConfig.typescript;
  var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;

  // Tokenizer

  var keywords = function(){
    function kw(type) {return {type: type, style: "keyword"};}
    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
    var operator = kw("operator"), atom = {type: "atom", style: "atom"};

    var jsKeywords = {
      "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
      "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
      "var": kw("var"), "const": kw("var"), "let": kw("var"),
      "function": kw("function"), "catch": kw("catch"),
      "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
      "in": operator, "typeof": operator, "instanceof": operator,
      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
      "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
      "yield": C, "export": kw("export"), "import": kw("import"), "extends": C
    };

    // Extend the 'normal' keywords with the TypeScript language extensions
    if (isTS) {
      var type = {type: "variable", style: "variable-3"};
      var tsKeywords = {
        // object-like things
        "interface": kw("interface"),
        "extends": kw("extends"),
        "constructor": kw("constructor"),

        // scope modifiers
        "public": kw("public"),
        "private": kw("private"),
        "protected": kw("protected"),
        "static": kw("static"),

        // types
        "string": type, "number": type, "bool": type, "any": type
      };

      for (var attr in tsKeywords) {
        jsKeywords[attr] = tsKeywords[attr];
      }
    }

    return jsKeywords;
  }();

  var isOperatorChar = /[+\-*&%=<>!?|~^]/;
  var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;

  function readRegexp(stream) {
    var escaped = false, next, inSet = false;
    while ((next = stream.next()) != null) {
      if (!escaped) {
        if (next == "/" && !inSet) return;
        if (next == "[") inSet = true;
        else if (inSet && next == "]") inSet = false;
      }
      escaped = !escaped && next == "\\";
    }
  }

  // Used as scratch variables to communicate multiple values without
  // consing up tons of objects.
  var type, content;
  function ret(tp, style, cont) {
    type = tp; content = cont;
    return style;
  }
  function tokenBase(stream, state) {
    var ch = stream.next();
    if (ch == '"' || ch == "'") {
      state.tokenize = tokenString(ch);
      return state.tokenize(stream, state);
    } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
      return ret("number", "number");
    } else if (ch == "." && stream.match("..")) {
      return ret("spread", "meta");
    } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
      return ret(ch);
    } else if (ch == "=" && stream.eat(">")) {
      return ret("=>", "operator");
    } else if (ch == "0" && stream.eat(/x/i)) {
      stream.eatWhile(/[\da-f]/i);
      return ret("number", "number");
    } else if (/\d/.test(ch)) {
      stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
      return ret("number", "number");
    } else if (ch == "/") {
      if (stream.eat("*")) {
        state.tokenize = tokenComment;
        return tokenComment(stream, state);
      } else if (stream.eat("/")) {
        stream.skipToEnd();
        return ret("comment", "comment");
      } else if (state.lastType == "operator" || state.lastType == "keyword c" ||
               state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
        readRegexp(stream);
        stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
        return ret("regexp", "string-2");
      } else {
        stream.eatWhile(isOperatorChar);
        return ret("operator", "operator", stream.current());
      }
    } else if (ch == "`") {
      state.tokenize = tokenQuasi;
      return tokenQuasi(stream, state);
    } else if (ch == "#") {
      stream.skipToEnd();
      return ret("error", "error");
    } else if (isOperatorChar.test(ch)) {
      stream.eatWhile(isOperatorChar);
      return ret("operator", "operator", stream.current());
    } else if (wordRE.test(ch)) {
      stream.eatWhile(wordRE);
      var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
      return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
                     ret("variable", "variable", word);
    }
  }

  function tokenString(quote) {
    return function(stream, state) {
      var escaped = false, next;
      if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
        state.tokenize = tokenBase;
        return ret("jsonld-keyword", "meta");
      }
      while ((next = stream.next()) != null) {
        if (next == quote && !escaped) break;
        escaped = !escaped && next == "\\";
      }
      if (!escaped) state.tokenize = tokenBase;
      return ret("string", "string");
    };
  }

  function tokenComment(stream, state) {
    var maybeEnd = false, ch;
    while (ch = stream.next()) {
      if (ch == "/" && maybeEnd) {
        state.tokenize = tokenBase;
        break;
      }
      maybeEnd = (ch == "*");
    }
    return ret("comment", "comment");
  }

  function tokenQuasi(stream, state) {
    var escaped = false, next;
    while ((next = stream.next()) != null) {
      if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
        state.tokenize = tokenBase;
        break;
      }
      escaped = !escaped && next == "\\";
    }
    return ret("quasi", "string-2", stream.current());
  }

  var brackets = "([{}])";
  // This is a crude lookahead trick to try and notice that we're
  // parsing the argument patterns for a fat-arrow function before we
  // actually hit the arrow token. It only works if the arrow is on
  // the same line as the arguments and there's no strange noise
  // (comments) in between. Fallback is to only notice when we hit the
  // arrow, and not declare the arguments as locals for the arrow
  // body.
  function findFatArrow(stream, state) {
    if (state.fatArrowAt) state.fatArrowAt = null;
    var arrow = stream.string.indexOf("=>", stream.start);
    if (arrow < 0) return;

    var depth = 0, sawSomething = false;
    for (var pos = arrow - 1; pos >= 0; --pos) {
      var ch = stream.string.charAt(pos);
      var bracket = brackets.indexOf(ch);
      if (bracket >= 0 && bracket < 3) {
        if (!depth) { ++pos; break; }
        if (--depth == 0) break;
      } else if (bracket >= 3 && bracket < 6) {
        ++depth;
      } else if (wordRE.test(ch)) {
        sawSomething = true;
      } else if (sawSomething && !depth) {
        ++pos;
        break;
      }
    }
    if (sawSomething && !depth) state.fatArrowAt = pos;
  }

  // Parser

  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};

  function JSLexical(indented, column, type, align, prev, info) {
    this.indented = indented;
    this.column = column;
    this.type = type;
    this.prev = prev;
    this.info = info;
    if (align != null) this.align = align;
  }

  function inScope(state, varname) {
    for (var v = state.localVars; v; v = v.next)
      if (v.name == varname) return true;
    for (var cx = state.context; cx; cx = cx.prev) {
      for (var v = cx.vars; v; v = v.next)
        if (v.name == varname) return true;
    }
  }

  function parseJS(state, style, type, content, stream) {
    var cc = state.cc;
    // Communicate our context to the combinators.
    // (Less wasteful than consing up a hundred closures on every call.)
    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;

    if (!state.lexical.hasOwnProperty("align"))
      state.lexical.align = true;

    while(true) {
      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
      if (combinator(type, content)) {
        while(cc.length && cc[cc.length - 1].lex)
          cc.pop()();
        if (cx.marked) return cx.marked;
        if (type == "variable" && inScope(state, content)) return "variable-2";
        return style;
      }
    }
  }

  // Combinator utils

  var cx = {state: null, column: null, marked: null, cc: null};
  function pass() {
    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
  }
  function cont() {
    pass.apply(null, arguments);
    return true;
  }
  function register(varname) {
    function inList(list) {
      for (var v = list; v; v = v.next)
        if (v.name == varname) return true;
      return false;
    }
    var state = cx.state;
    if (state.context) {
      cx.marked = "def";
      if (inList(state.localVars)) return;
      state.localVars = {name: varname, next: state.localVars};
    } else {
      if (inList(state.globalVars)) return;
      if (parserConfig.globalVars)
        state.globalVars = {name: varname, next: state.globalVars};
    }
  }

  // Combinators

  var defaultVars = {name: "this", next: {name: "arguments"}};
  function pushcontext() {
    cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
    cx.state.localVars = defaultVars;
  }
  function popcontext() {
    cx.state.localVars = cx.state.context.vars;
    cx.state.context = cx.state.context.prev;
  }
  function pushlex(type, info) {
    var result = function() {
      var state = cx.state, indent = state.indented;
      if (state.lexical.type == "stat") indent = state.lexical.indented;
      else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
        indent = outer.indented;
      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
    };
    result.lex = true;
    return result;
  }
  function poplex() {
    var state = cx.state;
    if (state.lexical.prev) {
      if (state.lexical.type == ")")
        state.indented = state.lexical.indented;
      state.lexical = state.lexical.prev;
    }
  }
  poplex.lex = true;

  function expect(wanted) {
    function exp(type) {
      if (type == wanted) return cont();
      else if (wanted == ";") return pass();
      else return cont(exp);
    };
    return exp;
  }

  function statement(type, value) {
    if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
    if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
    if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
    if (type == "{") return cont(pushlex("}"), block, poplex);
    if (type == ";") return cont();
    if (type == "if") {
      if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
        cx.state.cc.pop()();
      return cont(pushlex("form"), expression, statement, poplex, maybeelse);
    }
    if (type == "function") return cont(functiondef);
    if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
    if (type == "variable") return cont(pushlex("stat"), maybelabel);
    if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
                                      block, poplex, poplex);
    if (type == "case") return cont(expression, expect(":"));
    if (type == "default") return cont(expect(":"));
    if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
                                     statement, poplex, popcontext);
    if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
    if (type == "class") return cont(pushlex("form"), className, poplex);
    if (type == "export") return cont(pushlex("form"), afterExport, poplex);
    if (type == "import") return cont(pushlex("form"), afterImport, poplex);
    return pass(pushlex("stat"), expression, expect(";"), poplex);
  }
  function expression(type) {
    return expressionInner(type, false);
  }
  function expressionNoComma(type) {
    return expressionInner(type, true);
  }
  function expressionInner(type, noComma) {
    if (cx.state.fatArrowAt == cx.stream.start) {
      var body = noComma ? arrowBodyNoComma : arrowBody;
      if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
      else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
    }

    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
    if (type == "function") return cont(functiondef, maybeop);
    if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
    if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
    if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
    if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
    if (type == "{") return contCommasep(objprop, "}", null, maybeop);
    if (type == "quasi") { return pass(quasi, maybeop); }
    return cont();
  }
  function maybeexpression(type) {
    if (type.match(/[;\}\)\],]/)) return pass();
    return pass(expression);
  }
  function maybeexpressionNoComma(type) {
    if (type.match(/[;\}\)\],]/)) return pass();
    return pass(expressionNoComma);
  }

  function maybeoperatorComma(type, value) {
    if (type == ",") return cont(expression);
    return maybeoperatorNoComma(type, value, false);
  }
  function maybeoperatorNoComma(type, value, noComma) {
    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
    var expr = noComma == false ? expression : expressionNoComma;
    if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
    if (type == "operator") {
      if (/\+\+|--/.test(value)) return cont(me);
      if (value == "?") return cont(expression, expect(":"), expr);
      return cont(expr);
    }
    if (type == "quasi") { return pass(quasi, me); }
    if (type == ";") return;
    if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
    if (type == ".") return cont(property, me);
    if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
  }
  function quasi(type, value) {
    if (type != "quasi") return pass();
    if (value.slice(value.length - 2) != "${") return cont(quasi);
    return cont(expression, continueQuasi);
  }
  function continueQuasi(type) {
    if (type == "}") {
      cx.marked = "string-2";
      cx.state.tokenize = tokenQuasi;
      return cont(quasi);
    }
  }
  function arrowBody(type) {
    findFatArrow(cx.stream, cx.state);
    return pass(type == "{" ? statement : expression);
  }
  function arrowBodyNoComma(type) {
    findFatArrow(cx.stream, cx.state);
    return pass(type == "{" ? statement : expressionNoComma);
  }
  function maybelabel(type) {
    if (type == ":") return cont(poplex, statement);
    return pass(maybeoperatorComma, expect(";"), poplex);
  }
  function property(type) {
    if (type == "variable") {cx.marked = "property"; return cont();}
  }
  function objprop(type, value) {
    if (type == "variable" || cx.style == "keyword") {
      cx.marked = "property";
      if (value == "get" || value == "set") return cont(getterSetter);
      return cont(afterprop);
    } else if (type == "number" || type == "string") {
      cx.marked = jsonldMode ? "property" : (cx.style + " property");
      return cont(afterprop);
    } else if (type == "jsonld-keyword") {
      return cont(afterprop);
    } else if (type == "[") {
      return cont(expression, expect("]"), afterprop);
    }
  }
  function getterSetter(type) {
    if (type != "variable") return pass(afterprop);
    cx.marked = "property";
    return cont(functiondef);
  }
  function afterprop(type) {
    if (type == ":") return cont(expressionNoComma);
    if (type == "(") return pass(functiondef);
  }
  function commasep(what, end) {
    function proceed(type) {
      if (type == ",") {
        var lex = cx.state.lexical;
        if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
        return cont(what, proceed);
      }
      if (type == end) return cont();
      return cont(expect(end));
    }
    return function(type) {
      if (type == end) return cont();
      return pass(what, proceed);
    };
  }
  function contCommasep(what, end, info) {
    for (var i = 3; i < arguments.length; i++)
      cx.cc.push(arguments[i]);
    return cont(pushlex(end, info), commasep(what, end), poplex);
  }
  function block(type) {
    if (type == "}") return cont();
    return pass(statement, block);
  }
  function maybetype(type) {
    if (isTS && type == ":") return cont(typedef);
  }
  function typedef(type) {
    if (type == "variable"){cx.marked = "variable-3"; return cont();}
  }
  function vardef() {
    return pass(pattern, maybetype, maybeAssign, vardefCont);
  }
  function pattern(type, value) {
    if (type == "variable") { register(value); return cont(); }
    if (type == "[") return contCommasep(pattern, "]");
    if (type == "{") return contCommasep(proppattern, "}");
  }
  function proppattern(type, value) {
    if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
      register(value);
      return cont(maybeAssign);
    }
    if (type == "variable") cx.marked = "property";
    return cont(expect(":"), pattern, maybeAssign);
  }
  function maybeAssign(_type, value) {
    if (value == "=") return cont(expressionNoComma);
  }
  function vardefCont(type) {
    if (type == ",") return cont(vardef);
  }
  function maybeelse(type, value) {
    if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
  }
  function forspec(type) {
    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
  }
  function forspec1(type) {
    if (type == "var") return cont(vardef, expect(";"), forspec2);
    if (type == ";") return cont(forspec2);
    if (type == "variable") return cont(formaybeinof);
    return pass(expression, expect(";"), forspec2);
  }
  function formaybeinof(_type, value) {
    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
    return cont(maybeoperatorComma, forspec2);
  }
  function forspec2(type, value) {
    if (type == ";") return cont(forspec3);
    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
    return pass(expression, expect(";"), forspec3);
  }
  function forspec3(type) {
    if (type != ")") cont(expression);
  }
  function functiondef(type, value) {
    if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
    if (type == "variable") {register(value); return cont(functiondef);}
    if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
  }
  function funarg(type) {
    if (type == "spread") return cont(funarg);
    return pass(pattern, maybetype);
  }
  function className(type, value) {
    if (type == "variable") {register(value); return cont(classNameAfter);}
  }
  function classNameAfter(type, value) {
    if (value == "extends") return cont(expression, classNameAfter);
    if (type == "{") return cont(pushlex("}"), classBody, poplex);
  }
  function classBody(type, value) {
    if (type == "variable" || cx.style == "keyword") {
      cx.marked = "property";
      if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
      return cont(functiondef, classBody);
    }
    if (value == "*") {
      cx.marked = "keyword";
      return cont(classBody);
    }
    if (type == ";") return cont(classBody);
    if (type == "}") return cont();
  }
  function classGetterSetter(type) {
    if (type != "variable") return pass();
    cx.marked = "property";
    return cont();
  }
  function afterModule(type, value) {
    if (type == "string") return cont(statement);
    if (type == "variable") { register(value); return cont(maybeFrom); }
  }
  function afterExport(_type, value) {
    if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
    if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
    return pass(statement);
  }
  function afterImport(type) {
    if (type == "string") return cont();
    return pass(importSpec, maybeFrom);
  }
  function importSpec(type, value) {
    if (type == "{") return contCommasep(importSpec, "}");
    if (type == "variable") register(value);
    return cont();
  }
  function maybeFrom(_type, value) {
    if (value == "from") { cx.marked = "keyword"; return cont(expression); }
  }
  function arrayLiteral(type) {
    if (type == "]") return cont();
    return pass(expressionNoComma, maybeArrayComprehension);
  }
  function maybeArrayComprehension(type) {
    if (type == "for") return pass(comprehension, expect("]"));
    if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
    return pass(commasep(expressionNoComma, "]"));
  }
  function comprehension(type) {
    if (type == "for") return cont(forspec, comprehension);
    if (type == "if") return cont(expression, comprehension);
  }

  // Interface

  return {
    startState: function(basecolumn) {
      var state = {
        tokenize: tokenBase,
        lastType: "sof",
        cc: [],
        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
        localVars: parserConfig.localVars,
        context: parserConfig.localVars && {vars: parserConfig.localVars},
        indented: 0
      };
      if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
        state.globalVars = parserConfig.globalVars;
      return state;
    },

    token: function(stream, state) {
      if (stream.sol()) {
        if (!state.lexical.hasOwnProperty("align"))
          state.lexical.align = false;
        state.indented = stream.indentation();
        findFatArrow(stream, state);
      }
      if (state.tokenize != tokenComment && stream.eatSpace()) return null;
      var style = state.tokenize(stream, state);
      if (type == "comment") return style;
      state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
      return parseJS(state, style, type, content, stream);
    },

    indent: function(state, textAfter) {
      if (state.tokenize == tokenComment) return CodeMirror.Pass;
      if (state.tokenize != tokenBase) return 0;
      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
      // Kludge to prevent 'maybelse' from blocking lexical scope pops
      if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
        var c = state.cc[i];
        if (c == poplex) lexical = lexical.prev;
        else if (c != maybeelse) break;
      }
      if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
      if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
        lexical = lexical.prev;
      var type = lexical.type, closing = firstChar == type;

      if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
      else if (type == "form" && firstChar == "{") return lexical.indented;
      else if (type == "form") return lexical.indented + indentUnit;
      else if (type == "stat")
        return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
      else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
        return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
      else if (lexical.align) return lexical.column + (closing ? 0 : 1);
      else return lexical.indented + (closing ? 0 : indentUnit);
    },

    electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
    blockCommentStart: jsonMode ? null : "/*",
    blockCommentEnd: jsonMode ? null : "*/",
    lineComment: jsonMode ? null : "//",
    fold: "brace",

    helperType: jsonMode ? "json" : "javascript",
    jsonldMode: jsonldMode,
    jsonMode: jsonMode
  };
});

CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);

CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });

});

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("css", function(config, parserConfig) {
  if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");

  var indentUnit = config.indentUnit,
      tokenHooks = parserConfig.tokenHooks,
      mediaTypes = parserConfig.mediaTypes || {},
      mediaFeatures = parserConfig.mediaFeatures || {},
      propertyKeywords = parserConfig.propertyKeywords || {},
      nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
      colorKeywords = parserConfig.colorKeywords || {},
      valueKeywords = parserConfig.valueKeywords || {},
      fontProperties = parserConfig.fontProperties || {},
      allowNested = parserConfig.allowNested;

  var type, override;
  function ret(style, tp) { type = tp; return style; }

  // Tokenizers

  function tokenBase(stream, state) {
    var ch = stream.next();
    if (tokenHooks[ch]) {
      var result = tokenHooks[ch](stream, state);
      if (result !== false) return result;
    }
    if (ch == "@") {
      stream.eatWhile(/[\w\\\-]/);
      return ret("def", stream.current());
    } else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
      return ret(null, "compare");
    } else if (ch == "\"" || ch == "'") {
      state.tokenize = tokenString(ch);
      return state.tokenize(stream, state);
    } else if (ch == "#") {
      stream.eatWhile(/[\w\\\-]/);
      return ret("atom", "hash");
    } else if (ch == "!") {
      stream.match(/^\s*\w*/);
      return ret("keyword", "important");
    } else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
      stream.eatWhile(/[\w.%]/);
      return ret("number", "unit");
    } else if (ch === "-") {
      if (/[\d.]/.test(stream.peek())) {
        stream.eatWhile(/[\w.%]/);
        return ret("number", "unit");
      } else if (stream.match(/^\w+-/)) {
        return ret("meta", "meta");
      }
    } else if (/[,+>*\/]/.test(ch)) {
      return ret(null, "select-op");
    } else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
      return ret("qualifier", "qualifier");
    } else if (/[:;{}\[\]\(\)]/.test(ch)) {
      return ret(null, ch);
    } else if (ch == "u" && stream.match("rl(")) {
      stream.backUp(1);
      state.tokenize = tokenParenthesized;
      return ret("property", "word");
    } else if (/[\w\\\-]/.test(ch)) {
      stream.eatWhile(/[\w\\\-]/);
      return ret("property", "word");
    } else {
      return ret(null, null);
    }
  }

  function tokenString(quote) {
    return function(stream, state) {
      var escaped = false, ch;
      while ((ch = stream.next()) != null) {
        if (ch == quote && !escaped) {
          if (quote == ")") stream.backUp(1);
          break;
        }
        escaped = !escaped && ch == "\\";
      }
      if (ch == quote || !escaped && quote != ")") state.tokenize = null;
      return ret("string", "string");
    };
  }

  function tokenParenthesized(stream, state) {
    stream.next(); // Must be '('
    if (!stream.match(/\s*[\"\')]/, false))
      state.tokenize = tokenString(")");
    else
      state.tokenize = null;
    return ret(null, "(");
  }

  // Context management

  function Context(type, indent, prev) {
    this.type = type;
    this.indent = indent;
    this.prev = prev;
  }

  function pushContext(state, stream, type) {
    state.context = new Context(type, stream.indentation() + indentUnit, state.context);
    return type;
  }

  function popContext(state) {
    state.context = state.context.prev;
    return state.context.type;
  }

  function pass(type, stream, state) {
    return states[state.context.type](type, stream, state);
  }
  function popAndPass(type, stream, state, n) {
    for (var i = n || 1; i > 0; i--)
      state.context = state.context.prev;
    return pass(type, stream, state);
  }

  // Parser

  function wordAsValue(stream) {
    var word = stream.current().toLowerCase();
    if (valueKeywords.hasOwnProperty(word))
      override = "atom";
    else if (colorKeywords.hasOwnProperty(word))
      override = "keyword";
    else
      override = "variable";
  }

  var states = {};

  states.top = function(type, stream, state) {
    if (type == "{") {
      return pushContext(state, stream, "block");
    } else if (type == "}" && state.context.prev) {
      return popContext(state);
    } else if (type == "@media") {
      return pushContext(state, stream, "media");
    } else if (type == "@font-face") {
      return "font_face_before";
    } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
      return "keyframes";
    } else if (type && type.charAt(0) == "@") {
      return pushContext(state, stream, "at");
    } else if (type == "hash") {
      override = "builtin";
    } else if (type == "word") {
      override = "tag";
    } else if (type == "variable-definition") {
      return "maybeprop";
    } else if (type == "interpolation") {
      return pushContext(state, stream, "interpolation");
    } else if (type == ":") {
      return "pseudo";
    } else if (allowNested && type == "(") {
      return pushContext(state, stream, "parens");
    }
    return state.context.type;
  };

  states.block = function(type, stream, state) {
    if (type == "word") {
      var word = stream.current().toLowerCase();
      if (propertyKeywords.hasOwnProperty(word)) {
        override = "property";
        return "maybeprop";
      } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
        override = "string-2";
        return "maybeprop";
      } else if (allowNested) {
        override = stream.match(/^\s*:/, false) ? "property" : "tag";
        return "block";
      } else {
        override += " error";
        return "maybeprop";
      }
    } else if (type == "meta") {
      return "block";
    } else if (!allowNested && (type == "hash" || type == "qualifier")) {
      override = "error";
      return "block";
    } else {
      return states.top(type, stream, state);
    }
  };

  states.maybeprop = function(type, stream, state) {
    if (type == ":") return pushContext(state, stream, "prop");
    return pass(type, stream, state);
  };

  states.prop = function(type, stream, state) {
    if (type == ";") return popContext(state);
    if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
    if (type == "}" || type == "{") return popAndPass(type, stream, state);
    if (type == "(") return pushContext(state, stream, "parens");

    if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
      override += " error";
    } else if (type == "word") {
      wordAsValue(stream);
    } else if (type == "interpolation") {
      return pushContext(state, stream, "interpolation");
    }
    return "prop";
  };

  states.propBlock = function(type, _stream, state) {
    if (type == "}") return popContext(state);
    if (type == "word") { override = "property"; return "maybeprop"; }
    return state.context.type;
  };

  states.parens = function(type, stream, state) {
    if (type == "{" || type == "}") return popAndPass(type, stream, state);
    if (type == ")") return popContext(state);
    if (type == "(") return pushContext(state, stream, "parens");
    if (type == "word") wordAsValue(stream);
    return "parens";
  };

  states.pseudo = function(type, stream, state) {
    if (type == "word") {
      override = "variable-3";
      return state.context.type;
    }
    return pass(type, stream, state);
  };

  states.media = function(type, stream, state) {
    if (type == "(") return pushContext(state, stream, "media_parens");
    if (type == "}") return popAndPass(type, stream, state);
    if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");

    if (type == "word") {
      var word = stream.current().toLowerCase();
      if (word == "only" || word == "not" || word == "and")
        override = "keyword";
      else if (mediaTypes.hasOwnProperty(word))
        override = "attribute";
      else if (mediaFeatures.hasOwnProperty(word))
        override = "property";
      else
        override = "error";
    }
    return state.context.type;
  };

  states.media_parens = function(type, stream, state) {
    if (type == ")") return popContext(state);
    if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
    return states.media(type, stream, state);
  };

  states.font_face_before = function(type, stream, state) {
    if (type == "{")
      return pushContext(state, stream, "font_face");
    return pass(type, stream, state);
  };

  states.font_face = function(type, stream, state) {
    if (type == "}") return popContext(state);
    if (type == "word") {
      if (!fontProperties.hasOwnProperty(stream.current().toLowerCase()))
        override = "error";
      else
        override = "property";
      return "maybeprop";
    }
    return "font_face";
  };

  states.keyframes = function(type, stream, state) {
    if (type == "word") { override = "variable"; return "keyframes"; }
    if (type == "{") return pushContext(state, stream, "top");
    return pass(type, stream, state);
  };

  states.at = function(type, stream, state) {
    if (type == ";") return popContext(state);
    if (type == "{" || type == "}") return popAndPass(type, stream, state);
    if (type == "word") override = "tag";
    else if (type == "hash") override = "builtin";
    return "at";
  };

  states.interpolation = function(type, stream, state) {
    if (type == "}") return popContext(state);
    if (type == "{" || type == ";") return popAndPass(type, stream, state);
    if (type != "variable") override = "error";
    return "interpolation";
  };

  return {
    startState: function(base) {
      return {tokenize: null,
              state: "top",
              context: new Context("top", base || 0, null)};
    },

    token: function(stream, state) {
      if (!state.tokenize && stream.eatSpace()) return null;
      var style = (state.tokenize || tokenBase)(stream, state);
      if (style && typeof style == "object") {
        type = style[1];
        style = style[0];
      }
      override = style;
      state.state = states[state.state](type, stream, state);
      return override;
    },

    indent: function(state, textAfter) {
      var cx = state.context, ch = textAfter && textAfter.charAt(0);
      var indent = cx.indent;
      if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
      if (cx.prev &&
          (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "font_face") ||
           ch == ")" && (cx.type == "parens" || cx.type == "media_parens") ||
           ch == "{" && (cx.type == "at" || cx.type == "media"))) {
        indent = cx.indent - indentUnit;
        cx = cx.prev;
      }
      return indent;
    },

    electricChars: "}",
    blockCommentStart: "/*",
    blockCommentEnd: "*/",
    fold: "brace"
  };
});

  function keySet(array) {
    var keys = {};
    for (var i = 0; i < array.length; ++i) {
      keys[array[i]] = true;
    }
    return keys;
  }

  var mediaTypes_ = [
    "all", "aural", "braille", "handheld", "print", "projection", "screen",
    "tty", "tv", "embossed"
  ], mediaTypes = keySet(mediaTypes_);

  var mediaFeatures_ = [
    "width", "min-width", "max-width", "height", "min-height", "max-height",
    "device-width", "min-device-width", "max-device-width", "device-height",
    "min-device-height", "max-device-height", "aspect-ratio",
    "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
    "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
    "max-color", "color-index", "min-color-index", "max-color-index",
    "monochrome", "min-monochrome", "max-monochrome", "resolution",
    "min-resolution", "max-resolution", "scan", "grid"
  ], mediaFeatures = keySet(mediaFeatures_);

  var propertyKeywords_ = [
    "align-content", "align-items", "align-self", "alignment-adjust",
    "alignment-baseline", "anchor-point", "animation", "animation-delay",
    "animation-direction", "animation-duration", "animation-fill-mode",
    "animation-iteration-count", "animation-name", "animation-play-state",
    "animation-timing-function", "appearance", "azimuth", "backface-visibility",
    "background", "background-attachment", "background-clip", "background-color",
    "background-image", "background-origin", "background-position",
    "background-repeat", "background-size", "baseline-shift", "binding",
    "bleed", "bookmark-label", "bookmark-level", "bookmark-state",
    "bookmark-target", "border", "border-bottom", "border-bottom-color",
    "border-bottom-left-radius", "border-bottom-right-radius",
    "border-bottom-style", "border-bottom-width", "border-collapse",
    "border-color", "border-image", "border-image-outset",
    "border-image-repeat", "border-image-slice", "border-image-source",
    "border-image-width", "border-left", "border-left-color",
    "border-left-style", "border-left-width", "border-radius", "border-right",
    "border-right-color", "border-right-style", "border-right-width",
    "border-spacing", "border-style", "border-top", "border-top-color",
    "border-top-left-radius", "border-top-right-radius", "border-top-style",
    "border-top-width", "border-width", "bottom", "box-decoration-break",
    "box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
    "caption-side", "clear", "clip", "color", "color-profile", "column-count",
    "column-fill", "column-gap", "column-rule", "column-rule-color",
    "column-rule-style", "column-rule-width", "column-span", "column-width",
    "columns", "content", "counter-increment", "counter-reset", "crop", "cue",
    "cue-after", "cue-before", "cursor", "direction", "display",
    "dominant-baseline", "drop-initial-after-adjust",
    "drop-initial-after-align", "drop-initial-before-adjust",
    "drop-initial-before-align", "drop-initial-size", "drop-initial-value",
    "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
    "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
    "float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
    "font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
    "font-stretch", "font-style", "font-synthesis", "font-variant",
    "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
    "font-variant-ligatures", "font-variant-numeric", "font-variant-position",
    "font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
    "grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end",
    "grid-column-start", "grid-row", "grid-row-end", "grid-row-start",
    "grid-template", "grid-template-areas", "grid-template-columns",
    "grid-template-rows", "hanging-punctuation", "height", "hyphens",
    "icon", "image-orientation", "image-rendering", "image-resolution",
    "inline-box-align", "justify-content", "left", "letter-spacing",
    "line-break", "line-height", "line-stacking", "line-stacking-ruby",
    "line-stacking-shift", "line-stacking-strategy", "list-style",
    "list-style-image", "list-style-position", "list-style-type", "margin",
    "margin-bottom", "margin-left", "margin-right", "margin-top",
    "marker-offset", "marks", "marquee-direction", "marquee-loop",
    "marquee-play-count", "marquee-speed", "marquee-style", "max-height",
    "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
    "nav-left", "nav-right", "nav-up", "object-fit", "object-position",
    "opacity", "order", "orphans", "outline",
    "outline-color", "outline-offset", "outline-style", "outline-width",
    "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
    "padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
    "page", "page-break-after", "page-break-before", "page-break-inside",
    "page-policy", "pause", "pause-after", "pause-before", "perspective",
    "perspective-origin", "pitch", "pitch-range", "play-during", "position",
    "presentation-level", "punctuation-trim", "quotes", "region-break-after",
    "region-break-before", "region-break-inside", "region-fragment",
    "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
    "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
    "ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin",
    "shape-outside", "size", "speak", "speak-as", "speak-header",
    "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
    "tab-size", "table-layout", "target", "target-name", "target-new",
    "target-position", "text-align", "text-align-last", "text-decoration",
    "text-decoration-color", "text-decoration-line", "text-decoration-skip",
    "text-decoration-style", "text-emphasis", "text-emphasis-color",
    "text-emphasis-position", "text-emphasis-style", "text-height",
    "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
    "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
    "text-wrap", "top", "transform", "transform-origin", "transform-style",
    "transition", "transition-delay", "transition-duration",
    "transition-property", "transition-timing-function", "unicode-bidi",
    "vertical-align", "visibility", "voice-balance", "voice-duration",
    "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
    "voice-volume", "volume", "white-space", "widows", "width", "word-break",
    "word-spacing", "word-wrap", "z-index",
    // SVG-specific
    "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
    "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
    "color-interpolation", "color-interpolation-filters",
    "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
    "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
    "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
    "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
    "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
    "glyph-orientation-vertical", "text-anchor", "writing-mode"
  ], propertyKeywords = keySet(propertyKeywords_);

  var nonStandardPropertyKeywords_ = [
    "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
    "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
    "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
    "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
    "searchfield-results-decoration", "zoom"
  ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);

  var colorKeywords_ = [
    "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
    "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
    "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
    "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
    "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
    "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
    "darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
    "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
    "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
    "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
    "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
    "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
    "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
    "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
    "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
    "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
    "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
    "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
    "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
    "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
    "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
    "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
    "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
    "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
    "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
    "whitesmoke", "yellow", "yellowgreen"
  ], colorKeywords = keySet(colorKeywords_);

  var valueKeywords_ = [
    "above", "absolute", "activeborder", "activecaption", "afar",
    "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate",
    "always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
    "arabic-indic", "armenian", "asterisks", "auto", "avoid", "avoid-column", "avoid-page",
    "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
    "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
    "both", "bottom", "break", "break-all", "break-word", "button", "button-bevel",
    "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian",
    "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
    "cell", "center", "checkbox", "circle", "cjk-earthly-branch",
    "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
    "col-resize", "collapse", "column", "compact", "condensed", "contain", "content",
    "content-box", "context-menu", "continuous", "copy", "cover", "crop",
    "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal",
    "decimal-leading-zero", "default", "default-button", "destination-atop",
    "destination-in", "destination-out", "destination-over", "devanagari",
    "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted",
    "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
    "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
    "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
    "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
    "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
    "ethiopic-halehame-gez", "ethiopic-halehame-om-et",
    "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
    "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et",
    "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed",
    "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes",
    "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
    "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
    "help", "hidden", "hide", "higher", "highlight", "highlighttext",
    "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
    "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
    "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
    "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert",
    "italic", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer",
    "landscape", "lao", "large", "larger", "left", "level", "lighter",
    "line-through", "linear", "lines", "list-item", "listbox", "listitem",
    "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
    "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
    "lower-roman", "lowercase", "ltr", "malayalam", "match",
    "media-controls-background", "media-current-time-display",
    "media-fullscreen-button", "media-mute-button", "media-play-button",
    "media-return-to-realtime-button", "media-rewind-button",
    "media-seek-back-button", "media-seek-forward-button", "media-slider",
    "media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
    "media-volume-slider-container", "media-volume-sliderthumb", "medium",
    "menu", "menulist", "menulist-button", "menulist-text",
    "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
    "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
    "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
    "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
    "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
    "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
    "outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
    "painted", "page", "paused", "persian", "plus-darker", "plus-lighter", "pointer",
    "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button",
    "radio", "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region",
    "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba",
    "ridge", "right", "round", "row-resize", "rtl", "run-in", "running",
    "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield",
    "searchfield-cancel-button", "searchfield-decoration",
    "searchfield-results-button", "searchfield-results-decoration",
    "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
    "single", "skip-white-space", "slide", "slider-horizontal",
    "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
    "small", "small-caps", "small-caption", "smaller", "solid", "somali",
    "source-atop", "source-in", "source-out", "source-over", "space", "square",
    "square-button", "start", "static", "status-bar", "stretch", "stroke",
    "sub", "subpixel-antialiased", "super", "sw-resize", "table",
    "table-caption", "table-cell", "table-column", "table-column-group",
    "table-footer-group", "table-header-group", "table-row", "table-row-group",
    "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
    "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
    "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
    "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
    "transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
    "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
    "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
    "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
    "visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
    "window", "windowframe", "windowtext", "x-large", "x-small", "xor",
    "xx-large", "xx-small"
  ], valueKeywords = keySet(valueKeywords_);

  var fontProperties_ = [
    "font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
    "font-stretch", "font-weight", "font-style"
  ], fontProperties = keySet(fontProperties_);

  var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_)
    .concat(nonStandardPropertyKeywords_).concat(colorKeywords_).concat(valueKeywords_);
  CodeMirror.registerHelper("hintWords", "css", allWords);

  function tokenCComment(stream, state) {
    var maybeEnd = false, ch;
    while ((ch = stream.next()) != null) {
      if (maybeEnd && ch == "/") {
        state.tokenize = null;
        break;
      }
      maybeEnd = (ch == "*");
    }
    return ["comment", "comment"];
  }

  function tokenSGMLComment(stream, state) {
    if (stream.skipTo("-->")) {
      stream.match("-->");
      state.tokenize = null;
    } else {
      stream.skipToEnd();
    }
    return ["comment", "comment"];
  }

  CodeMirror.defineMIME("text/css", {
    mediaTypes: mediaTypes,
    mediaFeatures: mediaFeatures,
    propertyKeywords: propertyKeywords,
    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
    colorKeywords: colorKeywords,
    valueKeywords: valueKeywords,
    fontProperties: fontProperties,
    tokenHooks: {
      "<": function(stream, state) {
        if (!stream.match("!--")) return false;
        state.tokenize = tokenSGMLComment;
        return tokenSGMLComment(stream, state);
      },
      "/": function(stream, state) {
        if (!stream.eat("*")) return false;
        state.tokenize = tokenCComment;
        return tokenCComment(stream, state);
      }
    },
    name: "css"
  });

  CodeMirror.defineMIME("text/x-scss", {
    mediaTypes: mediaTypes,
    mediaFeatures: mediaFeatures,
    propertyKeywords: propertyKeywords,
    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
    colorKeywords: colorKeywords,
    valueKeywords: valueKeywords,
    fontProperties: fontProperties,
    allowNested: true,
    tokenHooks: {
      "/": function(stream, state) {
        if (stream.eat("/")) {
          stream.skipToEnd();
          return ["comment", "comment"];
        } else if (stream.eat("*")) {
          state.tokenize = tokenCComment;
          return tokenCComment(stream, state);
        } else {
          return ["operator", "operator"];
        }
      },
      ":": function(stream) {
        if (stream.match(/\s*\{/))
          return [null, "{"];
        return false;
      },
      "$": function(stream) {
        stream.match(/^[\w-]+/);
        if (stream.match(/^\s*:/, false))
          return ["variable-2", "variable-definition"];
        return ["variable-2", "variable"];
      },
      "#": function(stream) {
        if (!stream.eat("{")) return false;
        return [null, "interpolation"];
      }
    },
    name: "css",
    helperType: "scss"
  });

  CodeMirror.defineMIME("text/x-less", {
    mediaTypes: mediaTypes,
    mediaFeatures: mediaFeatures,
    propertyKeywords: propertyKeywords,
    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
    colorKeywords: colorKeywords,
    valueKeywords: valueKeywords,
    fontProperties: fontProperties,
    allowNested: true,
    tokenHooks: {
      "/": function(stream, state) {
        if (stream.eat("/")) {
          stream.skipToEnd();
          return ["comment", "comment"];
        } else if (stream.eat("*")) {
          state.tokenize = tokenCComment;
          return tokenCComment(stream, state);
        } else {
          return ["operator", "operator"];
        }
      },
      "@": function(stream) {
        if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
        stream.eatWhile(/[\w\\\-]/);
        if (stream.match(/^\s*:/, false))
          return ["variable-2", "variable-definition"];
        return ["variable-2", "variable"];
      },
      "&": function() {
        return ["atom", "atom"];
      }
    },
    name: "css",
    helperType: "less"
  });

});

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
  var htmlMode = CodeMirror.getMode(config, {name: "xml",
                                             htmlMode: true,
                                             multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
                                             multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag});
  var cssMode = CodeMirror.getMode(config, "css");

  var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
  scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i,
                    mode: CodeMirror.getMode(config, "javascript")});
  if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) {
    var conf = scriptTypesConf[i];
    scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)});
  }
  scriptTypes.push({matches: /./,
                    mode: CodeMirror.getMode(config, "text/plain")});

  function html(stream, state) {
    var tagName = state.htmlState.tagName;
    if (tagName) tagName = tagName.toLowerCase();
    var style = htmlMode.token(stream, state.htmlState);
    if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") {
      // Script block: mode to change to depends on type attribute
      var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i);
      scriptType = scriptType ? scriptType[1] : "";
      if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1);
      for (var i = 0; i < scriptTypes.length; ++i) {
        var tp = scriptTypes[i];
        if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) {
          if (tp.mode) {
            state.token = script;
            state.localMode = tp.mode;
            state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, ""));
          }
          break;
        }
      }
    } else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") {
      state.token = css;
      state.localMode = cssMode;
      state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
    }
    return style;
  }
  function maybeBackup(stream, pat, style) {
    var cur = stream.current();
    var close = cur.search(pat), m;
    if (close > -1) stream.backUp(cur.length - close);
    else if (m = cur.match(/<\/?$/)) {
      stream.backUp(cur.length);
      if (!stream.match(pat, false)) stream.match(cur);
    }
    return style;
  }
  function script(stream, state) {
    if (stream.match(/^<\/\s*script\s*>/i, false)) {
      state.token = html;
      state.localState = state.localMode = null;
      return html(stream, state);
    }
    return maybeBackup(stream, /<\/\s*script\s*>/,
                       state.localMode.token(stream, state.localState));
  }
  function css(stream, state) {
    if (stream.match(/^<\/\s*style\s*>/i, false)) {
      state.token = html;
      state.localState = state.localMode = null;
      return html(stream, state);
    }
    return maybeBackup(stream, /<\/\s*style\s*>/,
                       cssMode.token(stream, state.localState));
  }

  return {
    startState: function() {
      var state = htmlMode.startState();
      return {token: html, localMode: null, localState: null, htmlState: state};
    },

    copyState: function(state) {
      if (state.localState)
        var local = CodeMirror.copyState(state.localMode, state.localState);
      return {token: state.token, localMode: state.localMode, localState: local,
              htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
    },

    token: function(stream, state) {
      return state.token(stream, state);
    },

    indent: function(state, textAfter) {
      if (!state.localMode || /^\s*<\//.test(textAfter))
        return htmlMode.indent(state.htmlState, textAfter);
      else if (state.localMode.indent)
        return state.localMode.indent(state.localState, textAfter);
      else
        return CodeMirror.Pass;
    },

    innerMode: function(state) {
      return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
    }
  };
}, "xml", "javascript", "css");

CodeMirror.defineMIME("text/html", "htmlmixed");

});

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror", require("../xml/xml")));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror", "../xml/xml"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

  var htmlFound = CodeMirror.modes.hasOwnProperty("xml");
  var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain");
  var aliases = {
    html: "htmlmixed",
    js: "javascript",
    json: "application/json",
    c: "text/x-csrc",
    "c++": "text/x-c++src",
    java: "text/x-java",
    csharp: "text/x-csharp",
    "c#": "text/x-csharp",
    scala: "text/x-scala"
  };

  var getMode = (function () {
    var i, modes = {}, mimes = {}, mime;

    var list = [];
    for (var m in CodeMirror.modes)
      if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);
    for (i = 0; i < list.length; i++) {
      modes[list[i]] = list[i];
    }
    var mimesList = [];
    for (var m in CodeMirror.mimeModes)
      if (CodeMirror.mimeModes.propertyIsEnumerable(m))
        mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});
    for (i = 0; i < mimesList.length; i++) {
      mime = mimesList[i].mime;
      mimes[mime] = mimesList[i].mime;
    }

    for (var a in aliases) {
      if (aliases[a] in modes || aliases[a] in mimes)
        modes[a] = aliases[a];
    }

    return function (lang) {
      return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null;
    };
  }());

  // Should characters that affect highlighting be highlighted separate?
  // Does not include characters that will be output (such as `1.` and `-` for lists)
  if (modeCfg.highlightFormatting === undefined)
    modeCfg.highlightFormatting = false;

  // Maximum number of nested blockquotes. Set to 0 for infinite nesting.
  // Excess `>` will emit `error` token.
  if (modeCfg.maxBlockquoteDepth === undefined)
    modeCfg.maxBlockquoteDepth = 0;

  // Should underscores in words open/close em/strong?
  if (modeCfg.underscoresBreakWords === undefined)
    modeCfg.underscoresBreakWords = true;

  // Turn on fenced code blocks? ("```" to start/end)
  if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;

  // Turn on task lists? ("- [ ] " and "- [x] ")
  if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;

  var codeDepth = 0;

  var header   = 'header'
  ,   code     = 'comment'
  ,   quote    = 'quote'
  ,   list1    = 'variable-2'
  ,   list2    = 'variable-3'
  ,   list3    = 'keyword'
  ,   hr       = 'hr'
  ,   image    = 'tag'
  ,   formatting = 'formatting'
  ,   linkinline = 'link'
  ,   linkemail = 'link'
  ,   linktext = 'link'
  ,   linkhref = 'string'
  ,   em       = 'em'
  ,   strong   = 'strong';

  var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/
  ,   ulRE = /^[*\-+]\s+/
  ,   olRE = /^[0-9]+\.\s+/
  ,   taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
  ,   atxHeaderRE = /^#+/
  ,   setextHeaderRE = /^(?:\={1,}|-{1,})$/
  ,   textRE = /^[^#!\[\]*_\\<>` "'(]+/;

  function switchInline(stream, state, f) {
    state.f = state.inline = f;
    return f(stream, state);
  }

  function switchBlock(stream, state, f) {
    state.f = state.block = f;
    return f(stream, state);
  }


  // Blocks

  function blankLine(state) {
    // Reset linkTitle state
    state.linkTitle = false;
    // Reset EM state
    state.em = false;
    // Reset STRONG state
    state.strong = false;
    // Reset state.quote
    state.quote = 0;
    if (!htmlFound && state.f == htmlBlock) {
      state.f = inlineNormal;
      state.block = blockNormal;
    }
    // Reset state.trailingSpace
    state.trailingSpace = 0;
    state.trailingSpaceNewLine = false;
    // Mark this line as blank
    state.thisLineHasContent = false;
    return null;
  }

  function blockNormal(stream, state) {

    var sol = stream.sol();

    var prevLineIsList = (state.list !== false);
    if (state.list !== false && state.indentationDiff >= 0) { // Continued list
      if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
        state.indentation -= state.indentationDiff;
      }
      state.list = null;
    } else if (state.list !== false && state.indentation > 0) {
      state.list = null;
      state.listDepth = Math.floor(state.indentation / 4);
    } else if (state.list !== false) { // No longer a list
      state.list = false;
      state.listDepth = 0;
    }

    var match = null;
    if (state.indentationDiff >= 4) {
      state.indentation -= 4;
      stream.skipToEnd();
      return code;
    } else if (stream.eatSpace()) {
      return null;
    } else if (match = stream.match(atxHeaderRE)) {
      state.header = match[0].length <= 6 ? match[0].length : 6;
      if (modeCfg.highlightFormatting) state.formatting = "header";
      state.f = state.inline;
      return getType(state);
    } else if (state.prevLineHasContent && (match = stream.match(setextHeaderRE))) {
      state.header = match[0].charAt(0) == '=' ? 1 : 2;
      if (modeCfg.highlightFormatting) state.formatting = "header";
      state.f = state.inline;
      return getType(state);
    } else if (stream.eat('>')) {
      state.indentation++;
      state.quote = sol ? 1 : state.quote + 1;
      if (modeCfg.highlightFormatting) state.formatting = "quote";
      stream.eatSpace();
      return getType(state);
    } else if (stream.peek() === '[') {
      return switchInline(stream, state, footnoteLink);
    } else if (stream.match(hrRE, true)) {
      return hr;
    } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
      var listType = null;
      if (stream.match(ulRE, true)) {
        listType = 'ul';
      } else {
        stream.match(olRE, true);
        listType = 'ol';
      }
      state.indentation += 4;
      state.list = true;
      state.listDepth++;
      if (modeCfg.taskLists && stream.match(taskListRE, false)) {
        state.taskList = true;
      }
      state.f = state.inline;
      if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
      return getType(state);
    } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) {
      // try switching mode
      state.localMode = getMode(RegExp.$1);
      if (state.localMode) state.localState = state.localMode.startState();
      switchBlock(stream, state, local);
      if (modeCfg.highlightFormatting) state.formatting = "code-block";
      state.code = true;
      return getType(state);
    }

    return switchInline(stream, state, state.inline);
  }

  function htmlBlock(stream, state) {
    var style = htmlMode.token(stream, state.htmlState);
    if ((htmlFound && state.htmlState.tagStart === null && !state.htmlState.context) ||
        (state.md_inside && stream.current().indexOf(">") > -1)) {
      state.f = inlineNormal;
      state.block = blockNormal;
      state.htmlState = null;
    }
    return style;
  }

  function local(stream, state) {
    if (stream.sol() && stream.match(/^```/, true)) {
      state.localMode = state.localState = null;
      state.f = inlineNormal;
      state.block = blockNormal;
      if (modeCfg.highlightFormatting) state.formatting = "code-block";
      state.code = true;
      var returnType = getType(state);
      state.code = false;
      return returnType;
    } else if (state.localMode) {
      return state.localMode.token(stream, state.localState);
    } else {
      stream.skipToEnd();
      return code;
    }
  }

  // Inline
  function getType(state) {
    var styles = [];

    if (state.formatting) {
      styles.push(formatting);

      if (typeof state.formatting === "string") state.formatting = [state.formatting];

      for (var i = 0; i < state.formatting.length; i++) {
        styles.push(formatting + "-" + state.formatting[i]);

        if (state.formatting[i] === "header") {
          styles.push(formatting + "-" + state.formatting[i] + "-" + state.header);
        }

        // Add `formatting-quote` and `formatting-quote-#` for blockquotes
        // Add `error` instead if the maximum blockquote nesting depth is passed
        if (state.formatting[i] === "quote") {
          if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
            styles.push(formatting + "-" + state.formatting[i] + "-" + state.quote);
          } else {
            styles.push("error");
          }
        }
      }
    }

    if (state.taskOpen) {
      styles.push("meta");
      return styles.length ? styles.join(' ') : null;
    }
    if (state.taskClosed) {
      styles.push("property");
      return styles.length ? styles.join(' ') : null;
    }

    if (state.linkHref) {
      styles.push(linkhref);
      return styles.length ? styles.join(' ') : null;
    }

    if (state.strong) { styles.push(strong); }
    if (state.em) { styles.push(em); }

    if (state.linkText) { styles.push(linktext); }

    if (state.code) { styles.push(code); }

    if (state.header) { styles.push(header); styles.push(header + "-" + state.header); }

    if (state.quote) {
      styles.push(quote);

      // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
      if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
        styles.push(quote + "-" + state.quote);
      } else {
        styles.push(quote + "-" + modeCfg.maxBlockquoteDepth);
      }
    }

    if (state.list !== false) {
      var listMod = (state.listDepth - 1) % 3;
      if (!listMod) {
        styles.push(list1);
      } else if (listMod === 1) {
        styles.push(list2);
      } else {
        styles.push(list3);
      }
    }

    if (state.trailingSpaceNewLine) {
      styles.push("trailing-space-new-line");
    } else if (state.trailingSpace) {
      styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
    }

    return styles.length ? styles.join(' ') : null;
  }

  function handleText(stream, state) {
    if (stream.match(textRE, true)) {
      return getType(state);
    }
    return undefined;
  }

  function inlineNormal(stream, state) {
    var style = state.text(stream, state);
    if (typeof style !== 'undefined')
      return style;

    if (state.list) { // List marker (*, +, -, 1., etc)
      state.list = null;
      return getType(state);
    }

    if (state.taskList) {
      var taskOpen = stream.match(taskListRE, true)[1] !== "x";
      if (taskOpen) state.taskOpen = true;
      else state.taskClosed = true;
      if (modeCfg.highlightFormatting) state.formatting = "task";
      state.taskList = false;
      return getType(state);
    }

    state.taskOpen = false;
    state.taskClosed = false;

    if (state.header && stream.match(/^#+$/, true)) {
      if (modeCfg.highlightFormatting) state.formatting = "header";
      return getType(state);
    }

    // Get sol() value now, before character is consumed
    var sol = stream.sol();

    var ch = stream.next();

    if (ch === '\\') {
      stream.next();
      if (modeCfg.highlightFormatting) {
        var type = getType(state);
        return type ? type + " formatting-escape" : "formatting-escape";
      }
    }

    // Matches link titles present on next line
    if (state.linkTitle) {
      state.linkTitle = false;
      var matchCh = ch;
      if (ch === '(') {
        matchCh = ')';
      }
      matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
      var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
      if (stream.match(new RegExp(regex), true)) {
        return linkhref;
      }
    }

    // If this block is changed, it may need to be updated in GFM mode
    if (ch === '`') {
      var previousFormatting = state.formatting;
      if (modeCfg.highlightFormatting) state.formatting = "code";
      var t = getType(state);
      var before = stream.pos;
      stream.eatWhile('`');
      var difference = 1 + stream.pos - before;
      if (!state.code) {
        codeDepth = difference;
        state.code = true;
        return getType(state);
      } else {
        if (difference === codeDepth) { // Must be exact
          state.code = false;
          return t;
        }
        state.formatting = previousFormatting;
        return getType(state);
      }
    } else if (state.code) {
      return getType(state);
    }

    if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
      stream.match(/\[[^\]]*\]/);
      state.inline = state.f = linkHref;
      return image;
    }

    if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) {
      state.linkText = true;
      if (modeCfg.highlightFormatting) state.formatting = "link";
      return getType(state);
    }

    if (ch === ']' && state.linkText) {
      if (modeCfg.highlightFormatting) state.formatting = "link";
      var type = getType(state);
      state.linkText = false;
      state.inline = state.f = linkHref;
      return type;
    }

    if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
      state.f = state.inline = linkInline;
      if (modeCfg.highlightFormatting) state.formatting = "link";
      var type = getType(state);
      if (type){
        type += " ";
      } else {
        type = "";
      }
      return type + linkinline;
    }

    if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
      state.f = state.inline = linkInline;
      if (modeCfg.highlightFormatting) state.formatting = "link";
      var type = getType(state);
      if (type){
        type += " ";
      } else {
        type = "";
      }
      return type + linkemail;
    }

    if (ch === '<' && stream.match(/^\w/, false)) {
      if (stream.string.indexOf(">") != -1) {
        var atts = stream.string.substring(1,stream.string.indexOf(">"));
        if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) {
          state.md_inside = true;
        }
      }
      stream.backUp(1);
      state.htmlState = CodeMirror.startState(htmlMode);
      return switchBlock(stream, state, htmlBlock);
    }

    if (ch === '<' && stream.match(/^\/\w*?>/)) {
      state.md_inside = false;
      return "tag";
    }

    var ignoreUnderscore = false;
    if (!modeCfg.underscoresBreakWords) {
      if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
        var prevPos = stream.pos - 2;
        if (prevPos >= 0) {
          var prevCh = stream.string.charAt(prevPos);
          if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
            ignoreUnderscore = true;
          }
        }
      }
    }
    if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
      if (sol && stream.peek() === ' ') {
        // Do nothing, surrounded by newline and space
      } else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
        if (modeCfg.highlightFormatting) state.formatting = "strong";
        var t = getType(state);
        state.strong = false;
        return t;
      } else if (!state.strong && stream.eat(ch)) { // Add STRONG
        state.strong = ch;
        if (modeCfg.highlightFormatting) state.formatting = "strong";
        return getType(state);
      } else if (state.em === ch) { // Remove EM
        if (modeCfg.highlightFormatting) state.formatting = "em";
        var t = getType(state);
        state.em = false;
        return t;
      } else if (!state.em) { // Add EM
        state.em = ch;
        if (modeCfg.highlightFormatting) state.formatting = "em";
        return getType(state);
      }
    } else if (ch === ' ') {
      if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
        if (stream.peek() === ' ') { // Surrounded by spaces, ignore
          return getType(state);
        } else { // Not surrounded by spaces, back up pointer
          stream.backUp(1);
        }
      }
    }

    if (ch === ' ') {
      if (stream.match(/ +$/, false)) {
        state.trailingSpace++;
      } else if (state.trailingSpace) {
        state.trailingSpaceNewLine = true;
      }
    }

    return getType(state);
  }

  function linkInline(stream, state) {
    var ch = stream.next();

    if (ch === ">") {
      state.f = state.inline = inlineNormal;
      if (modeCfg.highlightFormatting) state.formatting = "link";
      var type = getType(state);
      if (type){
        type += " ";
      } else {
        type = "";
      }
      return type + linkinline;
    }

    stream.match(/^[^>]+/, true);

    return linkinline;
  }

  function linkHref(stream, state) {
    // Check if space, and return NULL if so (to avoid marking the space)
    if(stream.eatSpace()){
      return null;
    }
    var ch = stream.next();
    if (ch === '(' || ch === '[') {
      state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]");
      if (modeCfg.highlightFormatting) state.formatting = "link-string";
      state.linkHref = true;
      return getType(state);
    }
    return 'error';
  }

  function getLinkHrefInside(endChar) {
    return function(stream, state) {
      var ch = stream.next();

      if (ch === endChar) {
        state.f = state.inline = inlineNormal;
        if (modeCfg.highlightFormatting) state.formatting = "link-string";
        var returnState = getType(state);
        state.linkHref = false;
        return returnState;
      }

      if (stream.match(inlineRE(endChar), true)) {
        stream.backUp(1);
      }

      state.linkHref = true;
      return getType(state);
    };
  }

  function footnoteLink(stream, state) {
    if (stream.match(/^[^\]]*\]:/, false)) {
      state.f = footnoteLinkInside;
      stream.next(); // Consume [
      if (modeCfg.highlightFormatting) state.formatting = "link";
      state.linkText = true;
      return getType(state);
    }
    return switchInline(stream, state, inlineNormal);
  }

  function footnoteLinkInside(stream, state) {
    if (stream.match(/^\]:/, true)) {
      state.f = state.inline = footnoteUrl;
      if (modeCfg.highlightFormatting) state.formatting = "link";
      var returnType = getType(state);
      state.linkText = false;
      return returnType;
    }

    stream.match(/^[^\]]+/, true);

    return linktext;
  }

  function footnoteUrl(stream, state) {
    // Check if space, and return NULL if so (to avoid marking the space)
    if(stream.eatSpace()){
      return null;
    }
    // Match URL
    stream.match(/^[^\s]+/, true);
    // Check for link title
    if (stream.peek() === undefined) { // End of line, set flag to check next line
      state.linkTitle = true;
    } else { // More content on line, check if link title
      stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
    }
    state.f = state.inline = inlineNormal;
    return linkhref;
  }

  var savedInlineRE = [];
  function inlineRE(endChar) {
    if (!savedInlineRE[endChar]) {
      // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)
      endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
      // Match any non-endChar, escaped character, as well as the closing
      // endChar.
      savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')');
    }
    return savedInlineRE[endChar];
  }

  var mode = {
    startState: function() {
      return {
        f: blockNormal,

        prevLineHasContent: false,
        thisLineHasContent: false,

        block: blockNormal,
        htmlState: null,
        indentation: 0,

        inline: inlineNormal,
        text: handleText,

        formatting: false,
        linkText: false,
        linkHref: false,
        linkTitle: false,
        em: false,
        strong: false,
        header: 0,
        taskList: false,
        list: false,
        listDepth: 0,
        quote: 0,
        trailingSpace: 0,
        trailingSpaceNewLine: false
      };
    },

    copyState: function(s) {
      return {
        f: s.f,

        prevLineHasContent: s.prevLineHasContent,
        thisLineHasContent: s.thisLineHasContent,

        block: s.block,
        htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
        indentation: s.indentation,

        localMode: s.localMode,
        localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,

        inline: s.inline,
        text: s.text,
        formatting: false,
        linkTitle: s.linkTitle,
        em: s.em,
        strong: s.strong,
        header: s.header,
        taskList: s.taskList,
        list: s.list,
        listDepth: s.listDepth,
        quote: s.quote,
        trailingSpace: s.trailingSpace,
        trailingSpaceNewLine: s.trailingSpaceNewLine,
        md_inside: s.md_inside
      };
    },

    token: function(stream, state) {

      // Reset state.formatting
      state.formatting = false;

      if (stream.sol()) {
        var forceBlankLine = !!state.header;

        // Reset state.header
        state.header = 0;

        if (stream.match(/^\s*$/, true) || forceBlankLine) {
          state.prevLineHasContent = false;
          blankLine(state);
          return forceBlankLine ? this.token(stream, state) : null;
        } else {
          state.prevLineHasContent = state.thisLineHasContent;
          state.thisLineHasContent = true;
        }

        // Reset state.taskList
        state.taskList = false;

        // Reset state.code
        state.code = false;

        // Reset state.trailingSpace
        state.trailingSpace = 0;
        state.trailingSpaceNewLine = false;

        state.f = state.block;
        var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, '    ').length;
        var difference = Math.floor((indentation - state.indentation) / 4) * 4;
        if (difference > 4) difference = 4;
        var adjustedIndentation = state.indentation + difference;
        state.indentationDiff = adjustedIndentation - state.indentation;
        state.indentation = adjustedIndentation;
        if (indentation > 0) return null;
      }
      var result = state.f(stream, state);
      if (stream.start == stream.pos) return this.token(stream, state);
      else return result;
    },

    innerMode: function(state) {
      if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};
      if (state.localState) return {state: state.localState, mode: state.localMode};
      return {state: state, mode: mode};
    },

    blankLine: blankLine,

    getType: getType,

    fold: "markdown"
  };
  return mode;
}, "xml");

CodeMirror.defineMIME("text/x-markdown", "markdown");

});

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

// Utility function that allows modes to be combined. The mode given
// as the base argument takes care of most of the normal mode
// functionality, but a second (typically simple) mode is used, which
// can override the style of text. Both modes get to parse all of the
// text, but when both assign a non-null style to a piece of code, the
// overlay wins, unless the combine argument was true and not overridden,
// or state.overlay.combineTokens was true, in which case the styles are
// combined.

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.overlayMode = function(base, overlay, combine) {
  return {
    startState: function() {
      return {
        base: CodeMirror.startState(base),
        overlay: CodeMirror.startState(overlay),
        basePos: 0, baseCur: null,
        overlayPos: 0, overlayCur: null,
        lineSeen: null
      };
    },
    copyState: function(state) {
      return {
        base: CodeMirror.copyState(base, state.base),
        overlay: CodeMirror.copyState(overlay, state.overlay),
        basePos: state.basePos, baseCur: null,
        overlayPos: state.overlayPos, overlayCur: null
      };
    },

    token: function(stream, state) {
      if (stream.sol() || stream.string != state.lineSeen ||
          Math.min(state.basePos, state.overlayPos) < stream.start) {
        state.lineSeen = stream.string;
        state.basePos = state.overlayPos = stream.start;
      }

      if (stream.start == state.basePos) {
        state.baseCur = base.token(stream, state.base);
        state.basePos = stream.pos;
      }
      if (stream.start == state.overlayPos) {
        stream.pos = stream.start;
        state.overlayCur = overlay.token(stream, state.overlay);
        state.overlayPos = stream.pos;
      }
      stream.pos = Math.min(state.basePos, state.overlayPos);

      // state.overlay.combineTokens always takes precedence over combine,
      // unless set to null
      if (state.overlayCur == null) return state.baseCur;
      else if (state.baseCur != null &&
               state.overlay.combineTokens ||
               combine && state.overlay.combineTokens == null)
        return state.baseCur + " " + state.overlayCur;
      else return state.overlayCur;
    },

    indent: base.indent && function(state, textAfter) {
      return base.indent(state.base, textAfter);
    },
    electricChars: base.electricChars,

    innerMode: function(state) { return {state: state.base, mode: base}; },

    blankLine: function(state) {
      if (base.blankLine) base.blankLine(state.base);
      if (overlay.blankLine) overlay.blankLine(state.overlay);
    }
  };
};

});

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"), require("../markdown/markdown"), require("../../addon/mode/overlay"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror", "../markdown/markdown", "../../addon/mode/overlay"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("gfm", function(config, modeConfig) {
  var codeDepth = 0;
  function blankLine(state) {
    state.code = false;
    return null;
  }
  var gfmOverlay = {
    startState: function() {
      return {
        code: false,
        codeBlock: false,
        ateSpace: false
      };
    },
    copyState: function(s) {
      return {
        code: s.code,
        codeBlock: s.codeBlock,
        ateSpace: s.ateSpace
      };
    },
    token: function(stream, state) {
      state.combineTokens = null;

      // Hack to prevent formatting override inside code blocks (block and inline)
      if (state.codeBlock) {
        if (stream.match(/^```/)) {
          state.codeBlock = false;
          return null;
        }
        stream.skipToEnd();
        return null;
      }
      if (stream.sol()) {
        state.code = false;
      }
      if (stream.sol() && stream.match(/^```/)) {
        stream.skipToEnd();
        state.codeBlock = true;
        return null;
      }
      // If this block is changed, it may need to be updated in Markdown mode
      if (stream.peek() === '`') {
        stream.next();
        var before = stream.pos;
        stream.eatWhile('`');
        var difference = 1 + stream.pos - before;
        if (!state.code) {
          codeDepth = difference;
          state.code = true;
        } else {
          if (difference === codeDepth) { // Must be exact
            state.code = false;
          }
        }
        return null;
      } else if (state.code) {
        stream.next();
        return null;
      }
      // Check if space. If so, links can be formatted later on
      if (stream.eatSpace()) {
        state.ateSpace = true;
        return null;
      }
      if (stream.sol() || state.ateSpace) {
        state.ateSpace = false;
        if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
          // User/Project@SHA
          // User@SHA
          // SHA
          state.combineTokens = true;
          return "link";
        } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
          // User/Project#Num
          // User#Num
          // #Num
          state.combineTokens = true;
          return "link";
        }
      }
      if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i) &&
         stream.string.slice(stream.start - 2, stream.start) != "](") {
        // URLs
        // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
        // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
        state.combineTokens = true;
        return "link";
      }
      stream.next();
      return null;
    },
    blankLine: blankLine
  };

  var markdownConfig = {
    underscoresBreakWords: false,
    taskLists: true,
    fencedCodeBlocks: true
  };
  for (var attr in modeConfig) {
    markdownConfig[attr] = modeConfig[attr];
  }
  markdownConfig.name = "markdown";
  CodeMirror.defineMIME("gfmBase", markdownConfig);
  return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay);
}, "markdown");

});

/*!
 * ====================================================
 * kity - v2.0.0 - 2015-01-12
 * https://github.com/fex-team/kity
 * GitHub: https://github.com/fex-team/kity.git 
 * Copyright (c) 2015 Baidu FEX; Licensed BSD
 * ====================================================
 */

(function () {
var _p = {
    r: function(index) {
        if (_p[index].inited) {
            return _p[index].value;
        }
        if (typeof _p[index].value === "function") {
            var module = {
                exports: {}
            }, returnValue = _p[index].value(null, module.exports, module);
            _p[index].inited = true;
            _p[index].value = returnValue;
            if (returnValue !== undefined) {
                return returnValue;
            } else {
                for (var key in module.exports) {
                    if (module.exports.hasOwnProperty(key)) {
                        _p[index].inited = true;
                        _p[index].value = module.exports;
                        return module.exports;
                    }
                }
            }
        } else {
            _p[index].inited = true;
            return _p[index].value;
        }
    }
};

//src/animate/animator.js
/**
 * @fileOverview
 *
 * 提供基本的动画支持
 */
_p[0] = {
    value: function(require) {
        function parseTime(str) {
            var value = parseFloat(str, 10);
            if (/ms/.test(str)) {
                return value;
            }
            if (/s/.test(str)) {
                return value * 1e3;
            }
            if (/min/.test(str)) {
                return value * 60 * 1e3;
            }
            return value;
        }
        var Timeline = _p.r(8);
        var easingTable = _p.r(1);
        /**
     * @class kity.Animator
     * @catalog animate
     * @description 表示一个动画启动器，可以作用于不同的对象进行动画
     */
        var Animator = _p.r(11).createClass("Animator", {
            /**
         * @constructor
         * @for kity.Animator
         * @catalog animate
         *
         * @grammar new kity.Animator(beginValue, finishValue, setter)
         * @grammar new kity.Animator(option)
         *
         * @param  {any}      beginValue|opt.beginValue
         *     动画的起始值，允许的类型有数字、数组、字面量、kity.Point、kity.Vector、kity.Box、kity.Matrix
         *
         * @param  {any}      finishValue|opt.beginValue
         *     动画的结束值，类型应于起始值相同
         *
         * @param  {Function} setter|opt.setter
         *     值的使用函数，接受三个参数: function(target, value, timeline)
         *         target   {object}        动画的目标
         *         value    {any}           动画的当前值
         *         timeline {kity.Timeline} 动画当前的时间线对象
         */
            constructor: function(beginValue, finishValue, setter) {
                if (arguments.length == 1) {
                    var opt = arguments[0];
                    this.beginValue = opt.beginValue;
                    this.finishValue = opt.finishValue;
                    this.setter = opt.setter;
                } else {
                    this.beginValue = beginValue;
                    this.finishValue = finishValue;
                    this.setter = setter;
                }
            },
            /**
         * @method start()
         * @for kity.Animator
         * @description 使用当前的动画器启动在指定目标上启动动画
         *
         * @grammar start(target, duration, easing, delay, callback) => {kity.Timeline}
         * @grammar start(target, option) => {kity.Timeline}
         *
         * @param  {object} target
         *     启动动画的目标
         *
         * @param  {Number|String} duration|option.duration
         *     [Optional] 动画的持续时间，如 300、"300ms"、"1.5min"
         *
         * @param  {String|Function} easing|option.easing
         *     [Optional] 动画使用的缓动函数，如 "ease"、"linear"、"swing"
         *
         * @param  {Number|String} delay|option.delay
         *     [Optional] 动画的播放延迟时间
         *
         * @param  {Function} callback|option.callback
         *     [Optional] 动画结束后的回调函数
         *
         * @example
         *
         * ```js
         * var turnRed = new kity.Animator(
         *     new kity.Color('yellow'),
         *     new kity.Color('red'),
         *     function(target, value) {
         *         target.fill(value);
         *     });
         *
         * turnRed.start(rect, 300, 'ease', function() {
         *     console.log('I am red!');
         * });
         * ```
         */
            start: function(target, duration, easing, delay, callback) {
                if (arguments.length === 2 && typeof duration == "object") {
                    easing = duration.easing;
                    delay = duration.delay;
                    callback = duration.callback;
                    duration = duration.duration;
                }
                if (arguments.length === 4 && typeof delay == "function") {
                    callback = delay;
                    delay = 0;
                }
                var timeline = this.create(target, duration, easing, callback);
                delay = parseTime(delay);
                if (delay > 0) {
                    setTimeout(function() {
                        timeline.play();
                    }, delay);
                } else {
                    timeline.play();
                }
                return timeline;
            },
            /**
         * @method create()
         * @for kity.Animator
         * @description 使用当前的动画器为指定目标创建时间线
         *
         * @grammar create(target, duration, easing, callback) => {kity.Timeline}
         *
         * @param  {object}            target   要创建的时间线的目标
         * @param  {Number|String}     duration 要创建的时间线的长度，如 300、"5s"、"0.5min"
         * @param  {String|Function}   easing   要创建的时间线的缓动函数，如 'ease'、'linear'、'swing'
         * @param  {Function}          callback 时间线播放结束之后的回调函数
         *
         * @example
         *
         * ```js
         * var expand = new kity.Animator({
         *     beginValue: function(target) {
         *         return target.getBox();
         *     },
         *     finishValue: function(target) {
         *         return target.getBox().expand(100, 100, 100, 100);
         *     },
         *     setter: function(target, value) {
         *         target.setBox(value)
         *     }
         * });
         *
         * var timeline = expand.create(rect, 300);
         * timeline.repeat(3).play();
         * ```
         */
            create: function(target, duration, easing, callback) {
                var timeline;
                duration = duration && parseTime(duration) || Animator.DEFAULT_DURATION;
                easing = easing || Animator.DEFAULT_EASING;
                if (typeof easing == "string") {
                    easing = easingTable[easing];
                }
                timeline = new Timeline(this, target, duration, easing);
                if (typeof callback == "function") {
                    timeline.on("finish", callback);
                }
                return timeline;
            },
            /**
         * @method reverse()
         * @for kity.Animator
         * @grammar reverse() => {kity.Animator}
         * @description 创建一个与当前动画器相反的动画器
         *
         * @example
         *
         * ```js
         * var turnYellow = turnRed.reverse();
         * ```
         */
            reverse: function() {
                return new Animator(this.finishValue, this.beginValue, this.setter);
            }
        });
        Animator.DEFAULT_DURATION = 300;
        Animator.DEFAULT_EASING = "linear";
        var Shape = _p.r(60);
        _p.r(11).extendClass(Shape, {
            /**
         * @method animate()
         * @for kity.Shape
         * @description 在图形上播放使用指定的动画器播放动画，如果图形当前有动画正在播放，则会加入播放队列
         *
         * @grammar animate(animator, duration, easing, delay, callback)
         *
         * @param  {object}            animator 播放动画使用的动画器
         * @param  {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param  {Number|String}     delay    动画播放前的延时
         * @param  {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param  {Function}          callback 播放结束之后的回调函数
         *
         * @example
         *
         * ```js
         * rect.animate(turnRed, 300); // turnRect 是一个动画器
         * rect.animate(expand, 500);  // turnRect 播放结束后播放 expand
         * ```
         */
            animate: function(animator, duration, easing, delay, callback) {
                var queue = this._KityAnimateQueue = this._KityAnimateQueue || [];
                var timeline = animator.create(this, duration, easing, callback);
                function dequeue() {
                    queue.shift();
                    if (queue.length) {
                        setTimeout(queue[0].t.play.bind(queue[0].t), queue[0].d);
                    }
                }
                timeline.on("finish", dequeue);
                queue.push({
                    t: timeline,
                    d: delay
                });
                if (queue.length == 1) {
                    setTimeout(timeline.play.bind(timeline), delay);
                }
                return this;
            },
            /**
         * @method timeline()
         * @for kity.Shape
         * @description 获得当前正在播放的动画的时间线
         *
         * @grammar timeline() => {kity.Timeline}
         *
         * @example
         *
         * ```js
         * rect.timeline().repeat(2);
         * ```
         */
            timeline: function() {
                return this._KityAnimateQueue[0].t;
            },
            /**
         * @method stop()
         * @for kity.Shape
         * @description 停止当前正在播放的动画
         *
         * @grammar stop() => {this}
         *
         * @example
         *
         * ```js
         * rect.stop(); // 停止 rect 上的动画
         * ```
         */
            stop: function() {
                var queue = this._KityAnimateQueue;
                if (queue) {
                    while (queue.length) {
                        queue.shift().t.stop();
                    }
                }
                return this;
            }
        });
        return Animator;
    }
};

//src/animate/easing.js
/**
 * Kity Animate Easing modified from jQuery Easing
 * Author: techird
 * Changes:
 *     1. make easing functions standalone
 *     2. remove the 'x' parameter
 */
/* ============================================================
 * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
 *
 * Open source under the BSD License.
 *
 * Copyright © 2008 George McGinley Smith
 * All rights reserved.
 * https://raw.github.com/danro/jquery-easing/master/LICENSE
 * ======================================================== */
_p[1] = {
    value: function(require, exports, module) {
        var easings = {
            // t: current_time, b: begin_value, c: change_value, d: duration
            linear: function(t, b, c, d) {
                return c * (t / d) + b;
            },
            swing: function(t, b, c, d) {
                return easings.easeOutQuad(t, b, c, d);
            },
            ease: function(t, b, c, d) {
                return easings.easeInOutCubic(t, b, c, d);
            },
            easeInQuad: function(t, b, c, d) {
                return c * (t /= d) * t + b;
            },
            easeOutQuad: function(t, b, c, d) {
                return -c * (t /= d) * (t - 2) + b;
            },
            easeInOutQuad: function(t, b, c, d) {
                if ((t /= d / 2) < 1) return c / 2 * t * t + b;
                return -c / 2 * (--t * (t - 2) - 1) + b;
            },
            easeInCubic: function(t, b, c, d) {
                return c * (t /= d) * t * t + b;
            },
            easeOutCubic: function(t, b, c, d) {
                return c * ((t = t / d - 1) * t * t + 1) + b;
            },
            easeInOutCubic: function(t, b, c, d) {
                if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
                return c / 2 * ((t -= 2) * t * t + 2) + b;
            },
            easeInQuart: function(t, b, c, d) {
                return c * (t /= d) * t * t * t + b;
            },
            easeOutQuart: function(t, b, c, d) {
                return -c * ((t = t / d - 1) * t * t * t - 1) + b;
            },
            easeInOutQuart: function(t, b, c, d) {
                if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
                return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
            },
            easeInQuint: function(t, b, c, d) {
                return c * (t /= d) * t * t * t * t + b;
            },
            easeOutQuint: function(t, b, c, d) {
                return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
            },
            easeInOutQuint: function(t, b, c, d) {
                if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
                return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
            },
            easeInSine: function(t, b, c, d) {
                return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
            },
            easeOutSine: function(t, b, c, d) {
                return c * Math.sin(t / d * (Math.PI / 2)) + b;
            },
            easeInOutSine: function(t, b, c, d) {
                return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
            },
            easeInExpo: function(t, b, c, d) {
                return t === 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
            },
            easeOutExpo: function(t, b, c, d) {
                return t == d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
            },
            easeInOutExpo: function(t, b, c, d) {
                if (t === 0) return b;
                if (t == d) return b + c;
                if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
                return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
            },
            easeInCirc: function(t, b, c, d) {
                return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
            },
            easeOutCirc: function(t, b, c, d) {
                return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
            },
            easeInOutCirc: function(t, b, c, d) {
                if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
                return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
            },
            easeInElastic: function(t, b, c, d) {
                var s = 1.70158;
                var p = 0;
                var a = c;
                if (t === 0) return b;
                if ((t /= d) == 1) return b + c;
                if (!p) p = d * .3;
                if (a < Math.abs(c)) {
                    a = c;
                    s = p / 4;
                } else s = p / (2 * Math.PI) * Math.asin(c / a);
                return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
            },
            easeOutElastic: function(t, b, c, d) {
                var s = 1.70158;
                var p = 0;
                var a = c;
                if (t === 0) return b;
                if ((t /= d) == 1) return b + c;
                if (!p) p = d * .3;
                if (a < Math.abs(c)) {
                    a = c;
                    s = p / 4;
                } else s = p / (2 * Math.PI) * Math.asin(c / a);
                return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
            },
            easeInOutElastic: function(t, b, c, d) {
                var s = 1.70158;
                var p = 0;
                var a = c;
                if (t === 0) return b;
                if ((t /= d / 2) == 2) return b + c;
                if (!p) p = d * (.3 * 1.5);
                if (a < Math.abs(c)) {
                    a = c;
                    var s = p / 4;
                } else var s = p / (2 * Math.PI) * Math.asin(c / a);
                if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
                return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
            },
            easeInBack: function(t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                return c * (t /= d) * t * ((s + 1) * t - s) + b;
            },
            easeOutBack: function(t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
            },
            easeInOutBack: function(t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b;
                return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b;
            },
            easeInBounce: function(t, b, c, d) {
                return c - easings.easeOutBounce(d - t, 0, c, d) + b;
            },
            easeOutBounce: function(t, b, c, d) {
                if ((t /= d) < 1 / 2.75) {
                    return c * (7.5625 * t * t) + b;
                } else if (t < 2 / 2.75) {
                    return c * (7.5625 * (t -= 1.5 / 2.75) * t + .75) + b;
                } else if (t < 2.5 / 2.75) {
                    return c * (7.5625 * (t -= 2.25 / 2.75) * t + .9375) + b;
                } else {
                    return c * (7.5625 * (t -= 2.625 / 2.75) * t + .984375) + b;
                }
            },
            easeInOutBounce: function(t, b, c, d) {
                if (t < d / 2) return easings.easeInBounce(t * 2, 0, c, d) * .5 + b;
                return easings.easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
            }
        };
        return easings;
    }
};

/*
 *
 * TERMS OF USE - EASING EQUATIONS
 *
 * Open source under the BSD License.
 *
 * Copyright © 2001 Robert Penner
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 *
 * Neither the name of the author nor the names of contributors may be used to endorse
 * or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
//src/animate/frame.js
/**
 * @fileOverview
 *
 * 提供动画帧的基本支持
 */
_p[2] = {
    value: function(require, exports) {
        // 原生动画帧方法 polyfill
        var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(fn) {
            return setTimeout(fn, 1e3 / 60);
        };
        var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame || window.clearTimeout;
        // 上一个请求的原生动画帧 id
        var frameRequestId;
        // 等待执行的帧动作的集合，这些帧的方法将在下个原生动画帧同步执行
        var pendingFrames = [];
        /**
     * 添加一个帧到等待集合中
     *
     * 如果添加的帧是序列的第一个，至少有一个帧需要被执行，则会请求一个原生动画帧来执行
     */
        function pushFrame(frame) {
            if (pendingFrames.push(frame) === 1) {
                frameRequestId = requestAnimationFrame(executePendingFrames);
            }
        }
        /**
     * 执行所有等待帧
     */
        function executePendingFrames() {
            var frames = pendingFrames;
            pendingFrames = [];
            while (frames.length) {
                executeFrame(frames.pop());
            }
            frameRequestId = 0;
        }
        /**
     * @method kity.requestFrame
     * @catalog animate
     * @grammar kity.requestFrame(action) => {frame}
     * @description 请求一个帧，执行指定的动作。动作回调提供一些有用的信息
     *
     * @param {Function} action
     *
     *     要执行的动作，该动作回调有一个参数 frame，其中：
     *
     *     frame.time {Number}
     *         动作执行时的时间戳（ms）
     *
     *     frame.index {Number}
     *         当前执行的帧的编号（首帧为 0）
     *
     *     frame.dur {Number}
     *         上一帧至当前帧经过的时间，单位 ms
     *
     *     frame.elapsed {Number}
     *         从首帧开始到当前帧经过的时间，单位 ms
     *
     *     frame.action {Number}
     *         指向当前的帧处理函数
     *
     *     frame.next()
     *         表示下一帧继续执行。如果不调用该方法，将不会执行下一帧。
     *
     * @example
     *
     * ```js
     * kity.requestFrame(function(frame) {
     *     console.log('平均帧率:' + frame.elapsed / (frame.index + 1));
     *
     *     // 更新或渲染动作
     *
     *     frame.next(); //继续执行下一帧
     * });
     * ```
     */
        function requestFrame(action) {
            var frame = initFrame(action);
            pushFrame(frame);
            return frame;
        }
        /**
     * @method kity.releaseFrame
     * @catalog animate
     * @grammar kity.releaseFrame(frame)
     * @description 释放一个已经请求过的帧，如果该帧在等待集合里，将移除，下个动画帧不会执行释放的帧
     *
     * @param {frame} frame 使用 kity.requestFrame() 返回的帧
     *
     * @example
     *
     * ```js
     * var frame = kity.requestFrame(function() {....});
     * kity.releaseFrame(frame);
     * ```
     */
        function releaseFrame(frame) {
            var index = pendingFrames.indexOf(frame);
            if (~index) {
                pendingFrames.splice(index, 1);
            }
            if (pendingFrames.length === 0) {
                cancelAnimationFrame(frameRequestId);
            }
        }
        /**
     * 初始化一个帧，主要用于后续计算
     */
        function initFrame(action) {
            var frame = {
                index: 0,
                time: +new Date(),
                elapsed: 0,
                action: action,
                next: function() {
                    pushFrame(frame);
                }
            };
            return frame;
        }
        /**
     * 执行一个帧动作
     */
        function executeFrame(frame) {
            // 当前帧时间错
            var time = +new Date();
            // 当上一帧到当前帧经过的时间
            var dur = time - frame.time;
            //
            // http://stackoverflow.com/questions/13133434/requestanimationframe-detect-stop
            // 浏览器最小化或切换标签，requestAnimationFrame 不会执行。
            // 检测时间超过 200 ms（频率小于 5Hz ） 判定为计时器暂停，重置为一帧长度
            //
            if (dur > 200) {
                dur = 1e3 / 60;
            }
            frame.dur = dur;
            frame.elapsed += dur;
            frame.time = time;
            frame.action.call(null, frame);
            frame.index++;
        }
        // 暴露
        exports.requestFrame = requestFrame;
        exports.releaseFrame = releaseFrame;
    }
};

//src/animate/motionanimator.js
/**
 * @fileOverview
 *
 * 路径动画器，可以让一个物体沿着某个轨迹运动
 */
_p[3] = {
    value: function(require) {
        var Animator = _p.r(0);
        var g = _p.r(34);
        var Path = _p.r(46);
        var Shape = _p.r(60);
        /**
     * @class kity.MotionAnimator
     * @catalog animate
     * @base kity.Animator
     * @description 路径动画器，可以让一个物体沿着某个轨迹运动
     *
     * @example
     *
     * ```js
     * var motionAnimator = new MotionAnimator('M0,0C100,0,100,0,100,100L200,200');
     * motionAnimator.start(rect, 3000);
     * ```
     */
        var MotionAnimator = _p.r(11).createClass("MotionAnimator", {
            base: Animator,
            /**
         * @constructor
         * @for kity.MotionAnimator
         * @grammar new kity.MotionAnimator(path, doRotate)
         * @param {kity.Path|String|PathSegment} path 运动的轨迹，或者是 kity.Path 对象
         * @param {boolean} doRotate 是否让运动的目标沿着路径的切线方向旋转
         */
            constructor: function(path, doRotate) {
                var me = this;
                this.callBase({
                    beginValue: 0,
                    finishValue: 1,
                    setter: function(target, value) {
                        var path = me.motionPath instanceof Path ? me.motionPath.getPathData() : me.motionPath;
                        var point = g.pointAtPath(path, value);
                        target.setTranslate(point.x, point.y);
                        if (this.doRotate) target.setRotate(point.tan.getAngle());
                    }
                });
                /**
             * @property doRotate
             * @for kity.MotionAnimator
             * @type {boolean}
             * @description 是否让运动的目标沿着路径的切线方向旋转
             *
             * @example
             *
             * ```js
             * motionAnimator.doRotate = true; // 目标沿着切线方向旋转
             * ```
             */
                this.doRotate = doRotate;
                /**
             * @property motionPath
             * @for kity.MotionAnimator
             * @type  {kity.Path|String|PathSegment}
             * @description 运动沿着的路径，可以在动画过程中更新
             */
                this.motionPath = path;
            }
        });
        _p.r(11).extendClass(Shape, {
            /**
         * @method motion()
         * @catalog animate
         * @for kity.Shape
         * @description 让图形沿着指定的路径运动
         *
         * @grammar motion(path, duration, easing, delay, callback) => this
         *
         * @param {kity.Path|String|PathSegment} path 运动的轨迹，或者是 kity.Path 对象
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            motion: function(path, duration, easing, delay, callback) {
                return this.animate(new MotionAnimator(path), duration, easing, delay, callback);
            }
        });
        return MotionAnimator;
    }
};

//src/animate/opacityanimator.js
/**
 * @fileOverview
 *
 * 透明度动画器，让图形动画过度到指定的透明度。
 */
_p[4] = {
    value: function(require) {
        var Animator = _p.r(0);
        /**
     * @class kity.OpacityAnimator
     * @catalog animate
     * @base kity.Animator
     * @description 透明度动画器，让图形动画过度到指定的透明度
     */
        var OpacityAnimator = _p.r(11).createClass("OpacityAnimator", {
            base: Animator,
            /**
         * @constructor
         * @for kity.OpacityAnimator
         * @grammar new kity.OpacityAnimator(opacity)
         *
         * @param  {Number} opacity 目标透明度，取值范围 0 - 1
         */
            constructor: function(opacity) {
                this.callBase({
                    beginValue: function(target) {
                        return target.getOpacity();
                    },
                    finishValue: opacity,
                    setter: function(target, value) {
                        target.setOpacity(value);
                    }
                });
            }
        });
        var Shape = _p.r(60);
        _p.r(11).extendClass(Shape, {
            /**
         * @method fxOpacity()
         * @catalog animate
         * @for kity.Shape
         * @description 让图形的透明度以动画的形式过渡到指定的值
         *
         * @grammar fxOpacity(opacity, duration, easing, delay, callback) => {this}
         *
         * @param {Number}            opacity  动画的目标透明度
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fxOpacity: function(opacity, duration, easing, delay, callback) {
                return this.animate(new OpacityAnimator(opacity), duration, easing, delay, callback);
            },
            /**
         * @method fadeTo()
         * @catalog animate
         * @for kity.Shape
         * @description 让图形的透明度以动画的形式过渡到指定的值
         *
         * @grammar fadeTo(opacity, duration, easing, delay, callback) => {this}
         *
         * @param {Number}            opacity  动画的目标透明度
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fadeTo: function() {
                return this.fxOpacity.apply(this, arguments);
            },
            /**
         * @method fadeIn()
         * @catalog animate
         * @for kity.Shape
         * @description 让图形淡入
         *
         * @grammar fadeIn(duration, easing, delay, callback) => {this}
         *
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fadeIn: function() {
                return this.fxOpacity.apply(this, [ 1 ].concat([].slice.call(arguments)));
            },
            /**
         * @method fadeOut()
         * @catalog animate
         * @for kity.Shape
         * @description 让图形淡出
         *
         * @grammar fadeIn(duration, easing, delay, callback) => {this}
         *
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fadeOut: function() {
                return this.fxOpacity.apply(this, [ 0 ].concat([].slice.call(arguments)));
            }
        });
        return OpacityAnimator;
    }
};

//src/animate/pathanimator.js
/**
 * @fileOverview
 *
 * 路径补间动画器，让图形从一个形状变为另一个形状
 */
_p[5] = {
    value: function(require) {
        var Animator = _p.r(0);
        var g = _p.r(34);
        /**
     * @catalog animate
     *
     * @class kity.PathAnimator
     * @base kity.Animator
     * @description 路径补间动画器，让图形从一个形状变为另一个形状
     *
     * @example
     *
     * ```js
     * var path = new kity.Path('M0,0L0,100');
     * var pa = new kity.PathAnimator('M0,0C100,0,100,0,100,100');
     * pa.start(path, 300);
     * ```
     */
        var PathAnimator = _p.r(11).createClass("OpacityAnimator", {
            base: Animator,
            /**
         * @constructor
         * @for kity.PathAnimator
         *
         * @grammar new kity.Path.Animator(path)
         *
         * @param  {String|PathSegment} path 目标形状的路径数据
         *
         */
            constructor: function(path) {
                this.callBase({
                    beginValue: function(target) {
                        this.beginPath = target.getPathData();
                        return 0;
                    },
                    finishValue: 1,
                    setter: function(target, value) {
                        target.setPathData(g.pathTween(this.beginPath, path, value));
                    }
                });
            }
        });
        var Path = _p.r(46);
        _p.r(11).extendClass(Path, {
            /**
         * @catalog animate
         *
         * @method fxPath()
         * @for kity.Shape
         * @description 以动画的形式把路径变换为新路径
         *
         * @grammar fxPath(path, duration, easing, delay, callback) => {this}
         *
         * @param {String|PathSegment}   path     要变换新路径
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fxPath: function(path, duration, easing, delay, callback) {
                return this.animate(new PathAnimator(path), duration, easing, delay, callback);
            }
        });
        return PathAnimator;
    }
};

//src/animate/rotateanimator.js
/**
 * @fileOverview
 *
 * 提供支持目标旋转的动画器
 */
_p[6] = {
    value: function(require) {
        var Animator = _p.r(0);
        /**
     * @class kity.RotateAnimator
     * @base Animator
     * @description 提供支持目标旋转的动画器
     */
        var RotateAnimator = _p.r(11).createClass("RotateAnimator", {
            base: Animator,
            /**
         * @constructor
         * @for kity.RotateAnimator
         *
         * @grammar new kity.RotateAnimator(deg, ax, ay)
         *
         * @param  {Number} deg 要旋转的角度
         */
            constructor: function(deg) {
                this.callBase({
                    beginValue: 0,
                    finishValue: deg,
                    setter: function(target, value, timeline) {
                        var delta = timeline.getDelta();
                        target.rotate(delta, ax, ay);
                    }
                });
            }
        });
        var Shape = _p.r(60);
        _p.r(11).extendClass(Shape, {
            /**
         * @method fxRotate()
         * @for kity.Shape
         * @description 让目标以动画旋转指定的角度
         *
         * @grammar fxRotate(deg, duration, easing, delay) => {this}
         *
         * @param {Number}            deg      要旋转的角度
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fxRotate: function(deg, duration, easing, delay, callback) {
                return this.animate(new RotateAnimator(deg), duration, easing, delay, callback);
            }
        });
        return RotateAnimator;
    }
};

//src/animate/scaleanimator.js
/**
 * @fileOverview
 *
 * 提供支持目标缩放的动画器
 */
_p[7] = {
    value: function(require) {
        var Animator = _p.r(0);
        /**
     * @class kity.ScaleAnimator
     * @base kity.Animator
     * @description 提供支持目标缩放的动画器
     */
        var ScaleAnimator = _p.r(11).createClass("ScaleAnimator", {
            base: Animator,
            /**
         * @constructor
         * @for kity.ScaleAnimator
         *
         * @grammar new kity.ScaleAnimator(sx, sy)
         * @param  {Number} sx x 轴的缩放比例
         * @param  {Number} sy y 轴的缩放比例
         */
            constructor: function(sx, sy) {
                this.callBase({
                    beginValue: 0,
                    finishValue: 1,
                    setter: function(target, value, timeline) {
                        var delta = timeline.getDelta();
                        var kx = Math.pow(sx, delta);
                        var ky = Math.pow(sy, delta);
                        target.scale(ky, kx);
                    }
                });
            }
        });
        var Shape = _p.r(60);
        _p.r(11).extendClass(Shape, {
            /**
         * @method fxScale
         * @for kity.Shape
         * @description 动画缩放当前的图形
         *
         * @grammar fxScale(sx, sy, duration, easing, delay, callback) => {this}
         *
         * @param {Number} sx x 轴的缩放比例
         * @param {Number} sy y 轴的缩放比例
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fxScale: function(sx, sy, duration, easing, delay, callback) {
                return this.animate(new ScaleAnimator(sx, sy), duration, easing, delay, callback);
            }
        });
        return ScaleAnimator;
    }
};

//src/animate/timeline.js
/**
 * @fileOverview
 *
 * 动画时间线的实现
 */
_p[8] = {
    value: function(require) {
        var EventHandler = _p.r(33);
        var utils = _p.r(12);
        var frame = _p.r(2);
        function getPercentValue(b, f, p) {
            return utils.paralle(b, f, function(b, f) {
                return b + (f - b) * p;
            });
        }
        function getDelta(v1, v2) {
            return utils.paralle(v1, v2, function(v1, v2) {
                return v2 - v1;
            });
        }
        function TimelineEvent(timeline, type, param) {
            this.timeline = timeline;
            this.target = timeline.target;
            this.type = type;
            for (var name in param) {
                if (param.hasOwnProperty(name)) {
                    this[name] = param[name];
                }
            }
        }
        /**
     * @class kity.Timeline
     * @catalog animate
     * @mixins EventHandler
     * @description 动画时间线
     */
        var Timeline = _p.r(11).createClass("Timeline", {
            mixins: [ EventHandler ],
            /**
         * @constructor
         * @for kity.Timeline
         * @private
         * @description 时间线应该由动画器进行构造，不应手动创建
         *
         */
            constructor: function(animator, target, duration, easing) {
                this.callMixin();
                this.target = target;
                this.time = 0;
                this.duration = duration;
                this.easing = easing;
                this.animator = animator;
                this.beginValue = animator.beginValue;
                this.finishValue = animator.finishValue;
                this.setter = animator.setter;
                this.status = "ready";
            },
            /**
         * @private
         *
         * 让时间线进入下一帧
         */
            nextFrame: function(frame) {
                if (this.status != "playing") {
                    return;
                }
                this.time += frame.dur;
                this.setValue(this.getValue());
                if (this.time >= this.duration) {
                    this.timeUp();
                }
                frame.next();
            },
            /**
         * @method getPlayTime()
         * @for kity.Timeline
         * @grammar getPlayTime() => {Number}
         * @description 获得当前播放的时间，取值区间为 [0, duration]
         */
            getPlayTime: function() {
                return this.rollbacking ? this.duration - this.time : this.time;
            },
            /**
         * @method getTimeProportion()
         * @for kity.Timeline
         * @grammar getTimeProportion() => {Number}
         * @description 获得当前播放时间的比例，取值区间为 [0, 1]
         */
            getTimeProportion: function() {
                return this.getPlayTime() / this.duration;
            },
            /**
         * @method getValueProportion()
         * @for kity.Timeline
         * @grammar getValueProportion() => {Number}
         * @description 获得当前播放时间对应值的比例，取值区间为 [0, 1]；该值实际上是时间比例值经过缓动函数计算之后的值。
         */
            getValueProportion: function() {
                return this.easing(this.getPlayTime(), 0, 1, this.duration);
            },
            /**
         * @method getValue()
         * @for kity.Timeline
         * @grammar getValue() => {any}
         * @description 返回当前播放时间对应的值。
         */
            getValue: function() {
                var b = this.beginValue;
                var f = this.finishValue;
                var p = this.getValueProportion();
                return getPercentValue(b, f, p);
            },
            /**
         * @private
         *
         * 把值通过动画器的 setter 设置到目标上
         */
            setValue: function(value) {
                this.lastValue = this.currentValue;
                this.currentValue = value;
                this.setter.call(this.target, this.target, value, this);
            },
            /**
         * @method getDelta()
         * @for kity.Timeline
         * @grammar getDelta() => {any}
         * @description 返回当前值和上一帧的值的差值
         */
            getDelta: function() {
                this.lastValue = this.lastValue === undefined ? this.beginValue : this.lastValue;
                return getDelta(this.lastValue, this.currentValue);
            },
            /**
         * @method play()
         * @for kity.Timeline
         * @grammar play() => {this}
         * @description 让时间线播放，如果时间线还没开始，或者已停止、已结束，则重头播放；如果是已暂停，从暂停的位置继续播放
         */
            play: function() {
                var lastStatus = this.status;
                this.status = "playing";
                switch (lastStatus) {
                  case "ready":
                    if (utils.isFunction(this.beginValue)) {
                        this.beginValue = this.beginValue.call(this.target, this.target);
                    }
                    if (utils.isFunction(this.finishValue)) {
                        this.finishValue = this.finishValue.call(this.target, this.target);
                    }
                    this.time = 0;
                    this.setValue(this.beginValue);
                    this.frame = frame.requestFrame(this.nextFrame.bind(this));
                    break;

                  case "finished":
                  case "stoped":
                    this.time = 0;
                    this.frame = frame.requestFrame(this.nextFrame.bind(this));
                    break;

                  case "paused":
                    this.frame.next();
                }
                /**
             * @event play
             * @for kity.Timeline
             * @description 在时间线播放后触发
             *
             * @param {String} event.lastStatus
             *        表示播放前的上一个状态，可能取值为 'ready'、'finished'、'stoped'、'paused'
             */
                this.fire("play", new TimelineEvent(this, "play", {
                    lastStatus: lastStatus
                }));
                return this;
            },
            /**
         * @method pause()
         * @for kity.Timeline
         * @description 暂停当前的时间线
         *
         * @grammar pause() => {this}
         */
            pause: function() {
                this.status = "paused";
                /**
             * @event pause
             * @for kity.Timeline
             * @description 暂停事件，在时间线暂停时触发
             */
                this.fire("pause", new TimelineEvent(this, "pause"));
                frame.releaseFrame(this.frame);
                return this;
            },
            /**
         * @method stop()
         * @for kity.Timeline
         * @description 停止当前时间线
         *
         * @grammar stop() => {this}
         */
            stop: function() {
                this.status = "stoped";
                this.setValue(this.finishValue);
                this.rollbacking = false;
                /**
             * @event stop
             * @for kity.Timeline
             * @description 停止时间，在时间线停止时触发
             */
                this.fire("stop", new TimelineEvent(this, "stop"));
                frame.releaseFrame(this.frame);
                return this;
            },
            /**
         * @private
         *
         * 播放结束之后的处理
         */
            timeUp: function() {
                if (this.repeatOption) {
                    this.time = 0;
                    if (this.rollback) {
                        if (this.rollbacking) {
                            this.decreaseRepeat();
                            this.rollbacking = false;
                        } else {
                            this.rollbacking = true;
                            /**
                         * @event rollback
                         * @for kity.Timeline
                         * @description 回滚事件，在时间线回滚播放开始的时候触发
                         */
                            this.fire("rollback", new TimelineEvent(this, "rollback"));
                        }
                    } else {
                        this.decreaseRepeat();
                    }
                    if (!this.repeatOption) {
                        this.finish();
                    } else {
                        /**
                     * @event repeat
                     * @for kity.Timeline
                     * @description 循环事件，在时间线循环播放开始的时候触发
                     */
                        this.fire("repeat", new TimelineEvent(this, "repeat"));
                    }
                } else {
                    this.finish();
                }
            },
            /**
         * @private
         *
         * 决定播放结束的处理
         */
            finish: function() {
                this.setValue(this.finishValue);
                this.status = "finished";
                /**
             * @event finish
             * @for kity.Timeline
             * @description 结束事件，在时间线播放结束后触发（包括重复和回滚都结束）
             */
                this.fire("finish", new TimelineEvent(this, "finish"));
                frame.releaseFrame(this.frame);
            },
            /**
         * @private
         *
         *  循环次数递减
         */
            decreaseRepeat: function() {
                if (this.repeatOption !== true) {
                    this.repeatOption--;
                }
            },
            /**
         * @method repeat()
         * @for kity.Timeline
         * @description 设置时间线的重复选项
         *
         * @grammar repeat(repeat, rollback) => {this}
         *
         * @param  {Number|Boolean} repeat
         *     是否重复播放，设置为 true 无限循环播放，设置数值则循环指定的次数
         * @param  {Boolean} rollback
         *     指示是否要回滚播放。
         *     如果设置为真，一次事件到 duration 则一个来回算一次循环次数，否则播放完成一次算一次循环次数
         *
         */
            repeat: function(repeat, rollback) {
                this.repeatOption = repeat;
                this.rollback = rollback;
                return this;
            }
        });
        Timeline.requestFrame = frame.requestFrame;
        Timeline.releaseFrame = frame.releaseFrame;
        return Timeline;
    }
};

//src/animate/translateanimator.js
/**
 * @fileOverview
 *
 * 提供让图形移动的动画器
 */
_p[9] = {
    value: function(require) {
        var Animator = _p.r(0);
        /**
     * @class kity.TranslateAnimator
     * @base kity.Animator
     * @description 提供让图形移动的动画器
     */
        var TranslateAnimator = _p.r(11).createClass("TranslateAnimator", {
            base: Animator,
            /**
         * @constructor
         * @for kity.TranslateAnimator
         * @grammar new kity.TranslateAnimator(x, y)
         * @param  {Number} x x 方向上需要移动的距离
         * @param  {Number} y y 方向上需要移动的距离
         */
            constructor: function(x, y) {
                this.callBase({
                    x: 0,
                    y: 0
                }, {
                    x: x,
                    y: y
                }, function(target, value, timeline) {
                    var delta = timeline.getDelta();
                    target.translate(delta.x, delta.y);
                });
            }
        });
        var Shape = _p.r(60);
        _p.r(11).extendClass(Shape, {
            /**
         * @method fxTranslate()
         * @for kity.Shape
         * @description 让目标以动画平移指定的距离
         *
         * @grammar fxTranslate(x, y, duration, easing, delay, callback) => {this}
         *
         * @param {Number} x x 方向上需要移动的距离
         * @param {Number} y y 方向上需要移动的距离
         * @param {Number|String}     duration 动画的播放长度，如 300、"5s"、"0.5min"
         * @param {Number|String}     delay    动画播放前的延时
         * @param {String|Function}   easing   动画播放使用的缓动函数，如 'ease'、'linear'、'swing'
         * @param {Function}          callback 播放结束之后的回调函数
         */
            fxTranslate: function(x, y, duration, easing, delay, callback) {
                return this.animate(new TranslateAnimator(x, y), duration, easing, delay, callback);
            }
        });
        return TranslateAnimator;
    }
};

//src/core/browser.js
/**
 * @fileOverview
 *
 * 提供浏览器判断的一些字段
 */
_p[10] = {
    value: function() {
        /**
     * @class kity.Browser
     * @catalog core
     * @static
     * @description 提供浏览器信息
     */
        var browser = function() {
            var agent = navigator.userAgent.toLowerCase(), opera = window.opera, browser;
            // 浏览器对象
            browser = {
                /**
             * @property ie
             * @for kity.Browser
             * @description 判断是否为 IE 浏览器
             * @type {boolean}
             */
                ie: /(msie\s|trident.*rv:)([\w.]+)/.test(agent),
                /**
             * @property opera
             * @for kity.Browser
             * @description 判断是否为 Opera 浏览器
             * @type {boolean}
             */
                opera: !!opera && opera.version,
                /**
             * @property webkit
             * @for kity.Browser
             * @description 判断是否为 Webkit 内核的浏览器
             * @type {boolean}
             */
                webkit: agent.indexOf(" applewebkit/") > -1,
                /**
             * @property mac
             * @for kity.Browser
             * @description 判断是否为 Mac 下的浏览器
             * @type {boolean}
             */
                mac: agent.indexOf("macintosh") > -1
            };
            browser.gecko = navigator.product == "Gecko" && !browser.webkit && !browser.opera && !browser.ie;
            var version = 0;
            // Internet Explorer 6.0+
            if (browser.ie) {
                version = (agent.match(/(msie\s|trident.*rv:)([\w.]+)/)[2] || 0) * 1;
                browser.ie11Compat = document.documentMode == 11;
                browser.ie9Compat = document.documentMode == 9;
            }
            // Gecko.
            if (browser.gecko) {
                var geckoRelease = agent.match(/rv:([\d\.]+)/);
                if (geckoRelease) {
                    geckoRelease = geckoRelease[1].split(".");
                    version = geckoRelease[0] * 1e4 + (geckoRelease[1] || 0) * 100 + (geckoRelease[2] || 0) * 1;
                }
            }
            if (/chrome\/(\d+\.\d)/i.test(agent)) {
                /**
             * @property chrome
             * @for kity.Browser
             * @description 判断是否为 Chrome 浏览器
             * @type {boolean}
             */
                browser.chrome = +RegExp["$1"];
            }
            if (/(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) && !/chrome/i.test(agent)) {
                browser.safari = +(RegExp["$1"] || RegExp["$2"]);
            }
            // Opera 9.50+
            if (browser.opera) version = parseFloat(opera.version());
            // WebKit 522+ (Safari 3+)
            if (browser.webkit) version = parseFloat(agent.match(/ applewebkit\/(\d+)/)[1]);
            /**
         * @property version
         * @for kity.Browser
         * @description 获取当前浏览器的版本
         * @type {Number}
         */
            browser.version = version;
            browser.isCompatible = !browser.mobile && (browser.ie && version >= 6 || browser.gecko && version >= 10801 || browser.opera && version >= 9.5 || browser.air && version >= 1 || browser.webkit && version >= 522 || false);
            return browser;
        }();
        return browser;
    }
};

//src/core/class.js
/**
 * @fileOverview
 *
 * 提供 Kity 的 OOP 支持
 */
_p[11] = {
    value: function(require, exports) {
        /**
     * @class kity.Class
     * @catalog core
     * @description 所有 kity 类的基类
     * @abstract
     */
        function Class() {}
        exports.Class = Class;
        Class.__KityClassName = "Class";
        /**
     * @method base()
     * @for kity.Class
     * @protected
     * @grammar base(name, args...) => {any}
     * @description 调用父类指定名称的函数
     * @param {string} name 函数的名称
     * @param {parameter} args... 传递给父类函数的参数
     *
     * @example
     *
     * ```js
     * var Person = kity.createClass('Person', {
     *     toString: function() {
     *         return 'I am a person';
     *     }
     * });
     *
     * var Male = kity.createClass('Male', {
     *     base: Person,
     *
     *     toString: function() {
     *         return 'I am a man';
     *     },
     *
     *     speak: function() {
     *         return this.base('toString') + ',' + this.toString();
     *     }
     * })
     * ```
     */
        Class.prototype.base = function(name) {
            var caller = arguments.callee.caller;
            var method = caller.__KityMethodClass.__KityBaseClass.prototype[name];
            return method.apply(this, Array.prototype.slice.call(arguments, 1));
        };
        /**
     * @method callBase()
     * @for kity.Class
     * @protected
     * @grammar callBase(args...) => {any}
     * @description 调用父类同名函数
     * @param {parameter} args... 传递到父类同名函数的参数
     *
     * @example
     *
     * ```js
     * var Animal = kity.createClass('Animal', {
     *     constructor: function(name) {
     *         this.name = name;
     *     },
     *     toString: function() {
     *         return 'I am an animal name ' + this.name;
     *     }
     * });
     *
     * var Dog = kity.createClass('Dog', {
     *     constructor: function(name) {
     *         this.callBase(name);
     *     },
     *     toString: function() {
     *         return this.callBase() + ', a dog';
     *     }
     * });
     *
     * var dog = new Dog('Dummy');
     * console.log(dog.toString()); // "I am an animal name Dummy, a dog";
     * ```
     */
        Class.prototype.callBase = function() {
            var caller = arguments.callee.caller;
            var method = caller.__KityMethodClass.__KityBaseClass.prototype[caller.__KityMethodName];
            return method.apply(this, arguments);
        };
        Class.prototype.mixin = function(name) {
            var caller = arguments.callee.caller;
            var mixins = caller.__KityMethodClass.__KityMixins;
            if (!mixins) {
                return this;
            }
            var method = mixins[name];
            return method.apply(this, Array.prototype.slice.call(arguments, 1));
        };
        Class.prototype.callMixin = function() {
            var caller = arguments.callee.caller;
            var methodName = caller.__KityMethodName;
            var mixins = caller.__KityMethodClass.__KityMixins;
            if (!mixins) {
                return this;
            }
            var method = mixins[methodName];
            if (methodName == "constructor") {
                for (var i = 0, l = method.length; i < l; i++) {
                    method[i].call(this);
                }
                return this;
            } else {
                return method.apply(this, arguments);
            }
        };
        /**
     * @method pipe()
     * @for kity.Class
     * @grammar pipe() => {this}
     * @description 以当前对象为上线文以及管道函数的第一个参数，执行一个管道函数
     * @param  {Function} fn 进行管道操作的函数
     *
     * @example
     *
     * ```js
     * var rect = new kity.Rect().pipe(function() {
     *     this.setWidth(500);
     *     this.setHeight(300);
     * });
     * ```
     */
        Class.prototype.pipe = function(fn) {
            if (typeof fn == "function") {
                fn.call(this, this);
            }
            return this;
        };
        /**
     * @method getType()
     * @for kity.Class
     * @grammar getType() => {string}
     * @description 获得对象的类型
     *
     * @example
     *
     * ```js
     * var rect = new kity.Rect();
     * var circle = new kity.Circle();
     *
     * console.log(rect.getType()); // "Rect"
     * console.log(rect.getType()); // "Circle"
     * ```
     */
        Class.prototype.getType = function() {
            return this.__KityClassName;
        };
        /**
     * @method getClass()
     * @for kity.Class
     * @grammar getClass() => {Class}
     * @description 获得对象的类
     *
     * @example
     *
     * ```js
     * var rect = new kity.Rect();
     *
     * console.log(rect.getClass() === kity.Rect); // true
     * console.log(rect instanceof kity.Rect); // true
     * ```
     */
        Class.prototype.getClass = function() {
            return this.constructor;
        };
        // 检查基类是否调用了父类的构造函数
        // 该检查是弱检查，假如调用的代码被注释了，同样能检查成功（这个特性可用于知道建议调用，但是出于某些原因不想调用的情况）
        function checkBaseConstructorCall(targetClass, classname) {
            var code = targetClass.toString();
            if (!/this\.callBase/.test(code)) {
                throw new Error(classname + " : 类构造函数没有调用父类的构造函数！为了安全，请调用父类的构造函数");
            }
        }
        var KITY_INHERIT_FLAG = "__KITY_INHERIT_FLAG_" + +new Date();
        function inherit(constructor, BaseClass, classname) {
            var KityClass = eval("(function " + classname + "( __inherit__flag ) {" + "if( __inherit__flag != KITY_INHERIT_FLAG ) {" + "KityClass.__KityConstructor.apply(this, arguments);" + "}" + "this.__KityClassName = KityClass.__KityClassName;" + "})");
            KityClass.__KityConstructor = constructor;
            KityClass.prototype = new BaseClass(KITY_INHERIT_FLAG);
            for (var methodName in BaseClass.prototype) {
                if (BaseClass.prototype.hasOwnProperty(methodName) && methodName.indexOf("__Kity") !== 0) {
                    KityClass.prototype[methodName] = BaseClass.prototype[methodName];
                }
            }
            KityClass.prototype.constructor = KityClass;
            return KityClass;
        }
        function mixin(NewClass, mixins) {
            if (false === mixins instanceof Array) {
                return NewClass;
            }
            var i, length = mixins.length, proto, method;
            NewClass.__KityMixins = {
                constructor: []
            };
            for (i = 0; i < length; i++) {
                proto = mixins[i].prototype;
                for (method in proto) {
                    if (false === proto.hasOwnProperty(method) || method.indexOf("__Kity") === 0) {
                        continue;
                    }
                    if (method === "constructor") {
                        // constructor 特殊处理
                        NewClass.__KityMixins.constructor.push(proto[method]);
                    } else {
                        NewClass.prototype[method] = NewClass.__KityMixins[method] = proto[method];
                    }
                }
            }
            return NewClass;
        }
        function extend(BaseClass, extension) {
            if (extension.__KityClassName) {
                extension = extension.prototype;
            }
            for (var methodName in extension) {
                if (extension.hasOwnProperty(methodName) && methodName.indexOf("__Kity") && methodName != "constructor") {
                    var method = BaseClass.prototype[methodName] = extension[methodName];
                    method.__KityMethodClass = BaseClass;
                    method.__KityMethodName = methodName;
                }
            }
            return BaseClass;
        }
        /**
     * @method kity.createClass()
     * @grammar kity.createClass(classname, defines) => {Class}
     * @description 创建一个类
     * @param  {string} classname 类名，用于调试的时候查看，可选
     * @param  {object} defines   类定义
     *      defines.base {Class}
     *          定义的类的基类，如果不配置，则表示基类为 kity.Class
     *      defines.mixins {Class[]}
     *          定义的类要融合的类列表
     *      defines.constructor {Function}
     *          定义类的构造函数，如果父类显式定义了构造函数，需要在构造函数中使用 callBase() 方法调用父类的构造函数
     *      defines.* {Function}
     *          定义类的其它函数
     *
     * @example 创建一个类
     *
     * ```js
     * var Animal = kity.createClass('Animal', {
     *     constructor: function(name) {
     *         this.name = name;
     *     },
     *     toString: function() {
     *         return this.name;
     *     }
     * });
     *
     * var a = new Animal('kity');
     * console.log(a.toString()); // "kity"
     * ```
     *
     * @example 继承一个类
     *
     * ```js
     * var Cat = kity.createClass('Cat', {
     *     base: Animal,
     *     constructor: function(name, color) {
     *         // 调用父类构造函数
     *         this.callBase(name);
     *     },
     *     toString: function() {
     *         return 'A ' + this.color + ' cat, ' + this.callBase();
     *     }
     * });
     *
     * var cat = new Cat('kity', 'black');
     * console.log(cat.toString()); // "A black cat, kity"
     * ```
     *
     * @example 混合类的能力
     * ```js
     * var Walkable = kity.createClass('Walkable', {
     *     constructor: function() {
     *         this.speed = 'fast';
     *     },
     *     walk: function() {
     *         console.log('I am walking ' + this.speed);
     *     }
     * });
     *
     * var Dog = kity.createClass('Dog', {
     *     base: Animal,
     *     mixins: [Walkable],
     *     constructor: function(name) {
     *         this.callBase(name);
     *         this.callMixins();
     *     }
     * });
     *
     * var dog = new Dog('doggy');
     * console.log(dog.toString() + ' say:');
     * dog.walk();
     * ```
     */
        exports.createClass = function(classname, defines) {
            var constructor, NewClass, BaseClass;
            if (arguments.length === 1) {
                defines = arguments[0];
                classname = "AnonymousClass";
            }
            BaseClass = defines.base || Class;
            if (defines.hasOwnProperty("constructor")) {
                constructor = defines.constructor;
                if (BaseClass != Class) {
                    checkBaseConstructorCall(constructor, classname);
                }
            } else {
                constructor = function() {
                    this.callBase.apply(this, arguments);
                    this.callMixin.apply(this, arguments);
                };
            }
            NewClass = inherit(constructor, BaseClass, classname);
            NewClass = mixin(NewClass, defines.mixins);
            NewClass.__KityClassName = constructor.__KityClassName = classname;
            NewClass.__KityBaseClass = constructor.__KityBaseClass = BaseClass;
            NewClass.__KityMethodName = constructor.__KityMethodName = "constructor";
            NewClass.__KityMethodClass = constructor.__KityMethodClass = NewClass;
            // 下面这些不需要拷贝到原型链上
            delete defines.mixins;
            delete defines.constructor;
            delete defines.base;
            NewClass = extend(NewClass, defines);
            return NewClass;
        };
        /**
     * @method kity.extendClass()
     * @grammar kity.extendClass(clazz, extension) => {Class}
     * @description 拓展一个已有的类
     *
     * @example
     *
     * ```js
     * kity.extendClass(Dog, {
     *     spark: function() {
     *         console.log('wao wao wao!');
     *     }
     * });
     *
     * new Dog().spark(); // "wao wao wao!";
     * ```
     */
        exports.extendClass = extend;
    }
};

//src/core/utils.js
/**
 * @fileOverview
 *
 * 一些常用的工具方法
 */
_p[12] = {
    value: function() {
        /**
     * @class kity.Utils
     * @catalog core
     * @static
     * @description 提供常用的工具方法
     */
        var utils = {
            /**
         * @method each()
         * @for kity.Utils
         * @grammar each(obj, interator, context)
         * @param  {Object|Array} obj 要迭代的对象或数组
         * @param  {Function} iterator 迭代函数
         * @param  {Any} context  迭代函数的上下文
         *
         * @example 迭代数组
         *
         * ```js
         * kity.Utils.each([1, 2, 3, 4, 5], function(value, index, array) {
         *     console.log(value, index);
         * });
         * // 1, 0
         * // 2, 1
         * // 3, 2
         * // 4, 3
         * // 5, 4
         * ```
         *
         * @example 迭代对象
         *
         * ```js
         * var obj = {
         *     name: 'kity',
         *     version: '1.2.1'
         * };
         * var param = [];
         * kity.Utils.each(obj, function(value, key, obj) {
         *     param.push(key + '=' + value);
         * });
         * console.log(param.join('&')); // "name=kity&version=1.2.1"
         * ```
         */
            each: function each(obj, iterator, context) {
                if (obj === null) {
                    return;
                }
                if (obj.length === +obj.length) {
                    for (var i = 0, l = obj.length; i < l; i++) {
                        if (iterator.call(context, obj[i], i, obj) === false) {
                            return false;
                        }
                    }
                } else {
                    for (var key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            if (iterator.call(context, obj[key], key, obj) === false) {
                                return false;
                            }
                        }
                    }
                }
            },
            /**
         * @method extend()
         * @for kity.Utils
         * @grammar extend(target, sources..., notCover) => {object}
         * @description 把源对象的属性合并到目标对象上
         * @param {object} target 目标对象
         * @param {parameter} sources 源对象
         * @param {boolean} notCover 是否不要覆盖源对象已有的属性
         *
         * @example
         *
         * ```js
         * var a = {
         *     key1: 'a1',
         *     key2: 'a2'
         * };
         *
         * var b = {
         *     key2: 'b2',
         *     key3: 'b3'
         * };
         *
         * var c = {
         *     key4: 'c4'
         * };
         *
         * var d = kity.extend(a, b, c);
         *
         * console.log(d === a); // true
         * console.log(a); // {key1: 'a1', key2: 'b2', key3: 'b3', key4: 'c4'}
         * ```
         */
            extend: function extend(t) {
                var a = arguments, notCover = this.isBoolean(a[a.length - 1]) ? a[a.length - 1] : false, len = this.isBoolean(a[a.length - 1]) ? a.length - 1 : a.length;
                for (var i = 1; i < len; i++) {
                    var x = a[i];
                    for (var k in x) {
                        if (!notCover || !t.hasOwnProperty(k)) {
                            t[k] = x[k];
                        }
                    }
                }
                return t;
            },
            /**
         * @method deepExtend()
         * @for kity.Utils
         * @grammar deepExtend(target, sources..., notCover)
         * @description 把源对象的属性合并到目标对象上，如果属性是对象，会递归合并
         * @param {object} target 目标对象
         * @param {parameter} sources 源对象
         * @param {boolean} notCover 是否不要覆盖源对象已有的属性
         */
            deepExtend: function(t, s) {
                var a = arguments, notCover = this.isBoolean(a[a.length - 1]) ? a[a.length - 1] : false, len = this.isBoolean(a[a.length - 1]) ? a.length - 1 : a.length;
                for (var i = 1; i < len; i++) {
                    var x = a[i];
                    for (var k in x) {
                        if (!notCover || !t.hasOwnProperty(k)) {
                            if (this.isObject(t[k]) && this.isObject(x[k])) {
                                this.deepExtend(t[k], x[k], notCover);
                            } else {
                                t[k] = x[k];
                            }
                        }
                    }
                }
                return t;
            },
            /**
         * @method clone()
         * @for kity.Utils
         * @grammar clone(obj) => {object}
         * @description 返回一个对象的克隆副本（非深度复制）
         * @param  {object} obj 要克隆的对象
         *
         * @example
         *
         * ```js
         * var source = {
         *     key1: {
         *         key2: 'value2'
         *     },
         *     key3: 'value3'
         * };
         *
         * var target = kity.Utils.clone(source);
         *
         * console.log(target === source); // false
         * console.log(target.key1 === source.key1); // true
         * console.log(target.key3 === source.key3); // true
         * ```
         */
            clone: function clone(obj) {
                var cloned = {};
                for (var m in obj) {
                    if (obj.hasOwnProperty(m)) {
                        cloned[m] = obj[m];
                    }
                }
                return cloned;
            },
            /**
         * @method copy()
         * @for kity.Utils
         * @grammar copy(obj) => {object}
         * @description 返回一个对象的拷贝副本（深度复制）
         * @param  {object} obj 要拷贝的对象
         *
         * @example
         *
         * ```js
         * var source = {
         *     key1: {
         *         key2: 'value2'
         *     },
         *     key3: 'value3'
         * };
         *
         * var target = kity.Utils.copy(source);
         *
         * console.log(target === source); // false
         * console.log(target.key1 === source.key1); // false
         * console.log(target.key3 === source.key3); // true，因为是值类型
         * ```
         */
            copy: function copy(obj) {
                if (typeof obj !== "object") return obj;
                if (typeof obj === "function") return null;
                return JSON.parse(JSON.stringify(obj));
            },
            queryPath: function(path, obj) {
                var arr = path.split(".");
                var i = 0, tmp = obj, l = arr.length;
                while (i < l) {
                    if (arr[i] in tmp) {
                        tmp = tmp[arr[i]];
                        i++;
                        if (i >= l || tmp === undefined) {
                            return tmp;
                        }
                    } else {
                        return undefined;
                    }
                }
            },
            getValue: function(value, defaultValue) {
                return value !== undefined ? value : defaultValue;
            },
            /**
         * @method flatten()
         * @for kity.Utils
         * @grammar flatten(arr) => {Array}
         * @description 返回给定数组的扁平化版本
         * @param  {Array} arr 要扁平化的数组
         *
         * @example
         *
         * ```js
         * var flattened = kity.Utils.flatten([[1, 2], [2, 3], [[4, 5], [6, 7]]]);
         * console.log(flattened); // [1, 2, 3, 4, 5, 6, 7];
         * ```
         */
            flatten: function flatten(arr) {
                var result = [], length = arr.length, i;
                for (i = 0; i < length; i++) {
                    if (arr[i] instanceof Array) {
                        result = result.concat(utils.flatten(arr[i]));
                    } else {
                        result.push(arr[i]);
                    }
                }
                return result;
            },
            /**
         * @method paralle()
         * @for kity.Utils
         * @grammar paralle() => {Any}
         *
         * @description 平行地对 v1 和 v2 进行指定的操作
         *
         *    如果 v1 是数字，那么直接进行 op 操作
         *    如果 v1 是对象，那么返回一个对象，其元素是 v1 和 v2 同键值的每个元素平行地进行 op 操作的结果
         *    如果 v1 是数组，那么返回一个数组，其元素是 v1 和 v2 同索引的每个元素平行地进行 op 操作的结果
         *
         * @param  {Number|Object|Array} v1 第一个操作数
         * @param  {Number|Object|Array} v2 第二个操作数
         * @param  {Function} op 操作函数
         *
         *
         *
         * @example
         *
         * ```js
         * var a = {
         *     value1: 1,
         *     value2: 2,
         *     value3: [3, 4, 5]
         * };
         *
         * var b = {
         *     value1: 2,
         *     value2: 3,
         *     value3: [4, 5, 6]
         * };
         *
         * var c = kity.Utils.paralle(a, b, function(v1, v2) {
         *     return v1 + v2;
         * });
         *
         * console.log(c.value1); // 3
         * console.log(c.value2); // 5
         * console.log(c.value3); // [7, 9, 11]
         *
         * ```
         */
            paralle: function paralle(v1, v2, op) {
                var Class, field, index, name, value;
                // 数组
                if (v1 instanceof Array) {
                    value = [];
                    for (index = 0; index < v1.length; index++) {
                        value.push(utils.paralle(v1[index], v2[index], op));
                    }
                    return value;
                }
                // 对象
                if (v1 instanceof Object) {
                    // 如果值是一个支持原始表示的实例，获取其原始表示
                    Class = v1.getClass && v1.getClass();
                    if (Class && Class.parse) {
                        v1 = v1.valueOf();
                        v2 = v2.valueOf();
                        value = utils.paralle(v1, v2, op);
                        value = Class.parse(value);
                    } else {
                        value = {};
                        for (name in v1) {
                            if (v1.hasOwnProperty(name) && v2.hasOwnProperty(name)) {
                                value[name] = utils.paralle(v1[name], v2[name], op);
                            }
                        }
                    }
                    return value;
                }
                // 是否数字
                if (false === isNaN(parseFloat(v1))) {
                    return op(v1, v2);
                }
                return value;
            },
            /**
         * 创建 op 操作的一个平行化版本
         */
            parallelize: function parallelize(op) {
                return function(v1, v2) {
                    return utils.paralle(v1, v2, op);
                };
            }
        };
        /**
     * @method isString()
     * @for kity.Utils
     * @grammar isString(unknown) => {boolean}
     * @description 判断一个值是否为字符串类型
     * @param  {any} unknown 要判断的值
     */
        /**
     * @method isFunction()
     * @for kity.Utils
     * @grammar isFunction(unknown) => {boolean}
     * @description 判断一个值是否为函数类型
     * @param  {any} unknown 要判断的值
     */
        /**
     * @method isArray()
     * @for kity.Utils
     * @grammar isArray(unknown) => {boolean}
     * @description 判断一个值是否为数组类型
     * @param  {any} unknown 要判断的值
     */
        /**
     * @method isNumber()
     * @for kity.Utils
     * @grammar isNumber(unknown) => {boolean}
     * @description 判断一个值是否为数字类型
     * @param  {any} unknown 要判断的值
     */
        /**
     * @method isRegExp()
     * @for kity.Utils
     * @grammar isRegExp(unknown) => {boolean}
     * @description 判断一个值是否为正则表达式类型
     * @param  {any} unknown 要判断的值
     */
        /**
     * @method isObject()
     * @for kity.Utils
     * @grammar isObject(unknown) => {boolean}
     * @description 判断一个值是否为对象类型
     * @param  {any} unknown 要判断的值
     */
        /**
     * @method isBoolean()
     * @for kity.Utils
     * @grammar isBoolean(unknown) => {boolean}
     * @description 判断一个值是否为布尔类型
     * @param  {any} unknown 要判断的值
     */
        utils.each([ "String", "Function", "Array", "Number", "RegExp", "Object", "Boolean" ], function(v) {
            utils["is" + v] = function typeCheck(obj) {
                return Object.prototype.toString.apply(obj) == "[object " + v + "]";
            };
        });
        return utils;
    }
};

//src/filter/effect/colormatrixeffect.js
/**
 * 颜色矩阵运算效果封装
 */
_p[13] = {
    value: function(require, exports, module) {
        var Effect = _p.r(16), Utils = _p.r(12);
        var ColorMatrixEffect = _p.r(11).createClass("ColorMatrixEffect", {
            base: Effect,
            constructor: function(type, input) {
                this.callBase(Effect.NAME_COLOR_MATRIX);
                this.set("type", Utils.getValue(type, ColorMatrixEffect.TYPE_MATRIX));
                this.set("in", Utils.getValue(input, Effect.INPUT_SOURCE_GRAPHIC));
            }
        });
        Utils.extend(ColorMatrixEffect, {
            // 类型常量
            TYPE_MATRIX: "matrix",
            TYPE_SATURATE: "saturate",
            TYPE_HUE_ROTATE: "hueRotate",
            TYPE_LUMINANCE_TO_ALPHA: "luminanceToAlpha",
            // 矩阵常量
            MATRIX_ORIGINAL: "10000010000010000010".split("").join(" "),
            MATRIX_EMPTY: "00000000000000000000".split("").join(" ")
        });
        return ColorMatrixEffect;
    }
};

//src/filter/effect/compositeeffect.js
/**
 * 高斯模糊效果封装
 */
_p[14] = {
    value: function(require, exports, module) {
        var Effect = _p.r(16), Utils = _p.r(12);
        var CompositeEffect = _p.r(11).createClass("CompositeEffect", {
            base: Effect,
            constructor: function(operator, input, input2) {
                this.callBase(Effect.NAME_COMPOSITE);
                this.set("operator", Utils.getValue(operator, CompositeEffect.OPERATOR_OVER));
                if (input) {
                    this.set("in", input);
                }
                if (input2) {
                    this.set("in2", input2);
                }
            }
        });
        Utils.extend(CompositeEffect, {
            // operator 常量
            OPERATOR_OVER: "over",
            OPERATOR_IN: "in",
            OPERATOR_OUT: "out",
            OPERATOR_ATOP: "atop",
            OPERATOR_XOR: "xor",
            OPERATOR_ARITHMETIC: "arithmetic"
        });
        return CompositeEffect;
    }
};

//src/filter/effect/convolvematrixeffect.js
/**
 * 像素级别的矩阵卷积运算效果封装
 */
_p[15] = {
    value: function(require, exports, module) {
        var Effect = _p.r(16), Utils = _p.r(12);
        var ConvolveMatrixEffect = _p.r(11).createClass("ConvolveMatrixEffect", {
            base: Effect,
            constructor: function(edgeMode, input) {
                this.callBase(Effect.NAME_CONVOLVE_MATRIX);
                this.set("edgeMode", Utils.getValue(edgeMode, ConvolveMatrixEffect.MODE_DUPLICATE));
                this.set("in", Utils.getValue(input, Effect.INPUT_SOURCE_GRAPHIC));
            }
        });
        Utils.extend(ConvolveMatrixEffect, {
            MODE_DUPLICATE: "duplicate",
            MODE_WRAP: "wrap",
            MODE_NONE: "none"
        });
        return ConvolveMatrixEffect;
    }
};

//src/filter/effect/effect.js
/*
 * 效果类
 * 该类型的对象不存储任何内部属性， 所有操作都是针对该类对象所维护的节点进行的
 */
_p[16] = {
    value: function(require, exports, module) {
        var svg = _p.r(67), Effect = _p.r(11).createClass("Effect", {
            constructor: function(type) {
                this.node = svg.createNode(type);
            },
            getId: function() {
                return this.node.id;
            },
            setId: function(id) {
                this.node.id = id;
                return this;
            },
            set: function(key, value) {
                this.node.setAttribute(key, value);
                return this;
            },
            get: function(key) {
                return this.node.getAttribute(key);
            },
            getNode: function() {
                return this.node;
            },
            // 返回该效果的result
            toString: function() {
                return this.node.getAttribute("result") || "";
            }
        });
        _p.r(12).extend(Effect, {
            // 特效名称常量
            NAME_GAUSSIAN_BLUR: "feGaussianBlur",
            NAME_OFFSET: "feOffset",
            NAME_COMPOSITE: "feComposite",
            NAME_COLOR_MATRIX: "feColorMatrix",
            NAME_CONVOLVE_MATRIX: "feConvolveMatrix",
            // 输入常量
            INPUT_SOURCE_GRAPHIC: "SourceGraphic",
            INPUT_SOURCE_ALPHA: "SourceAlpha",
            INPUT_BACKGROUND_IMAGE: "BackgroundImage",
            INPUT_BACKGROUND_ALPHA: "BackgroundAlpha",
            INPUT_FILL_PAINT: "FillPaint",
            INPUT_STROKE_PAINT: "StrokePaint"
        });
        return Effect;
    }
};

//src/filter/effect/gaussianblureffect.js
/**
 * 高斯模糊效果封装
 */
_p[17] = {
    value: function(require, exports, module) {
        var Effect = _p.r(16), Utils = _p.r(12);
        return _p.r(11).createClass("GaussianblurEffect", {
            base: Effect,
            constructor: function(stdDeviation, input) {
                this.callBase(Effect.NAME_GAUSSIAN_BLUR);
                this.set("stdDeviation", Utils.getValue(stdDeviation, 1));
                this.set("in", Utils.getValue(input, Effect.INPUT_SOURCE_GRAPHIC));
            }
        });
    }
};

//src/filter/effect/offseteffect.js
/**
 * 偏移效果封装
 */
_p[18] = {
    value: function(require, exports, module) {
        var Effect = _p.r(16), Utils = _p.r(12);
        return _p.r(11).createClass("OffsetEffect", {
            base: Effect,
            constructor: function(dx, dy, input) {
                this.callBase(Effect.NAME_OFFSET);
                this.set("dx", Utils.getValue(dx, 0));
                this.set("dy", Utils.getValue(dy, 0));
                this.set("in", Utils.getValue(input, Effect.INPUT_SOURCE_GRAPHIC));
            }
        });
    }
};

//src/filter/effectcontainer.js
/*
 * Effect所用的container
 */
_p[19] = {
    value: function(require) {
        return _p.r(11).createClass("EffectContainer", {
            base: _p.r(29),
            addEffect: function(point, pos) {
                return this.addItem.apply(this, arguments);
            },
            prependEffect: function() {
                return this.prependItem.apply(this, arguments);
            },
            appendEffect: function() {
                return this.appendItem.apply(this, arguments);
            },
            removeEffect: function(pos) {
                return this.removeItem.apply(this, arguments);
            },
            addEffects: function() {
                return this.addItems.apply(this, arguments);
            },
            setEffects: function() {
                return this.setItems.apply(this, arguments);
            },
            getEffect: function() {
                return this.getItem.apply(this, arguments);
            },
            getEffects: function() {
                return this.getItems.apply(this, arguments);
            },
            getFirstEffect: function() {
                return this.getFirstItem.apply(this, arguments);
            },
            getLastEffect: function() {
                return this.getLastItem.apply(this, arguments);
            },
            handleAdd: function(effectItem, pos) {
                var count = this.getEffects().length, nextEffectItem = this.getItem(pos + 1);
                // 最后一个节点， 直接追加
                if (count === pos + 1) {
                    this.node.appendChild(effectItem.getNode());
                    return;
                }
                this.node.insertBefore(effectItem.getNode(), nextEffectItem.getNode());
            }
        });
    }
};

//src/filter/filter.js
/**
 * Filter 基类
 */
_p[20] = {
    value: function(require, exports, module) {
        var svg = _p.r(67);
        var Class = _p.r(11);
        var Filter = Class.createClass("Filter", {
            mixins: [ _p.r(19) ],
            constructor: function(x, y, width, height) {
                this.node = svg.createNode("filter");
                if (x !== undefined) {
                    this.set("x", x);
                }
                if (y !== undefined) {
                    this.set("y", y);
                }
                if (width !== undefined) {
                    this.set("width", width);
                }
                if (height !== undefined) {
                    this.set("height", height);
                }
            },
            getId: function() {
                return this.id;
            },
            setId: function(id) {
                this.node.id = id;
                return this;
            },
            set: function(key, value) {
                this.node.setAttribute(key, value);
                return this;
            },
            get: function(key) {
                return this.node.getAttribute(key);
            },
            getNode: function() {
                return this.node;
            }
        });
        var Shape = _p.r(60);
        Class.extendClass(Shape, {
            applyFilter: function(filter) {
                var filterId = filter.get("id");
                if (filterId) {
                    this.node.setAttribute("filter", "url(#" + filterId + ")");
                }
                return this;
            }
        });
        return Filter;
    }
};

//src/filter/gaussianblurfilter.js
/*
 * 高斯模糊滤镜
 */
_p[21] = {
    value: function(require, exports, module) {
        var GaussianblurEffect = _p.r(17);
        return _p.r(11).createClass("GaussianblurFilter", {
            base: _p.r(20),
            constructor: function(stdDeviation) {
                this.callBase();
                this.addEffect(new GaussianblurEffect(stdDeviation));
            }
        });
    }
};

//src/filter/projectionfilter.js
/*
 * 投影滤镜
 */
_p[22] = {
    value: function(require, exports, module) {
        var GaussianblurEffect = _p.r(17), Effect = _p.r(16), ColorMatrixEffect = _p.r(13), Color = _p.r(28), Utils = _p.r(12), CompositeEffect = _p.r(14), OffsetEffect = _p.r(18);
        return _p.r(11).createClass("ProjectionFilter", {
            base: _p.r(20),
            constructor: function(stdDeviation, dx, dy) {
                this.callBase();
                this.gaussianblurEffect = new GaussianblurEffect(stdDeviation, Effect.INPUT_SOURCE_ALPHA);
                this.gaussianblurEffect.set("result", "gaussianblur");
                this.addEffect(this.gaussianblurEffect);
                this.offsetEffect = new OffsetEffect(dx, dy, this.gaussianblurEffect);
                this.offsetEffect.set("result", "offsetBlur");
                this.addEffect(this.offsetEffect);
                this.colorMatrixEffect = new ColorMatrixEffect(ColorMatrixEffect.TYPE_MATRIX, this.offsetEffect);
                this.colorMatrixEffect.set("values", ColorMatrixEffect.MATRIX_ORIGINAL);
                this.colorMatrixEffect.set("result", "colorOffsetBlur");
                this.addEffect(this.colorMatrixEffect);
                this.compositeEffect = new CompositeEffect(CompositeEffect.OPERATOR_OVER, Effect.INPUT_SOURCE_GRAPHIC, this.colorMatrixEffect);
                this.addEffect(this.compositeEffect);
            },
            // 设置投影颜色
            setColor: function(color) {
                var matrix = null, originMatrix = null, colorValue = [];
                if (Utils.isString(color)) {
                    color = Color.parse(color);
                }
                if (!color) {
                    return this;
                }
                matrix = ColorMatrixEffect.MATRIX_EMPTY.split(" ");
                colorValue.push(color.get("r"));
                colorValue.push(color.get("g"));
                colorValue.push(color.get("b"));
                // rgb 分量更改
                for (var i = 0, len = colorValue.length; i < len; i++) {
                    matrix[i * 5 + 3] = colorValue[i] / 255;
                }
                // alpha 分量更改
                matrix[18] = color.get("a");
                this.colorMatrixEffect.set("values", matrix.join(" "));
                return this;
            },
            // 设置投影透明度
            setOpacity: function(opacity) {
                var matrix = this.colorMatrixEffect.get("values").split(" ");
                matrix[18] = opacity;
                this.colorMatrixEffect.set("values", matrix.join(" "));
                return this;
            },
            // 设置阴影偏移量
            setOffset: function(dx, dy) {
                this.setOffsetX(dx);
                this.setOffsetY(dy);
            },
            setOffsetX: function(dx) {
                this.offsetEffect.set("dx", dx);
            },
            setOffsetY: function(dy) {
                this.offsetEffect.set("dy", dy);
            },
            setDeviation: function(deviation) {
                this.gaussianblurEffect.set("stdDeviation", deviation);
            }
        });
    }
};

//src/graphic/bezier.js
/**
 * @fileOverview
 *
 * 贝塞尔曲线
 */
_p[23] = {
    value: function(require, exports, module) {
        /**
     * @class kity.Bezier
     * @mixins kity.PointContainer
     * @base kity.Path
     * @description 绘制和使用贝塞尔曲线。贝塞尔曲线作为一个贝塞尔点的容器，任何贝塞尔点的改变都会更改贝塞尔曲线的外观
     */
        return _p.r(11).createClass("Bezier", {
            mixins: [ _p.r(51) ],
            base: _p.r(46),
            /**
         * @constructor
         * @for kity.Bezier
         *
         * @grammar new kity.Bezier(bezierPoints)
         *
         * @param  {kity.BezierPoints[]} bezierPoints 贝塞尔点集合，每个元素应该是 {kity.BezierPoint} 类型
         *
         * @example
         *
         * ```js
         * var bezier = new kity.Bezier([
         *     new kity.BezierPoint(0, 0).setForward(100, 0),
         *     new kity.BezierPoint(100, 100).setBackward(100, 0)
         * ]);
         * ```
         */
            constructor: function(bezierPoints) {
                this.callBase();
                bezierPoints = bezierPoints || [];
                this.changeable = true;
                this.setBezierPoints(bezierPoints);
            },
            /**
         * @method getBezierPoints()
         * @for kity.Bezier
         * @description 返回当前贝塞尔曲线的贝塞尔点集合
         *
         * @grammar getBezierPoints() => {kity.BezierPoints[]}
         *
         */
            getBezierPoints: function() {
                return this.getPoints();
            },
            /**
         * @method setBezierPoints()
         * @for kity.Bezier
         * @description 设置当前贝塞尔曲线的贝塞尔点集合
         *
         * @grammar setBeizerPoints(bezierPoints) => {this}
         *
         * @param {kity.BezierPoint[]} bezierPoints 贝塞尔点集合
         */
            setBezierPoints: function(bezierPoints) {
                return this.setPoints(bezierPoints);
            },
            //当点集合发生变化时采取的动作
            onContainerChanged: function() {
                if (this.changeable) {
                    this.update();
                }
            },
            update: function() {
                var drawer = null, bezierPoints = this.getBezierPoints();
                //单独的一个点不画任何图形
                if (bezierPoints.length < 2) {
                    return;
                }
                drawer = this.getDrawer();
                drawer.clear();
                var vertex = bezierPoints[0].getVertex(), forward = null, backward = null;
                drawer.moveTo(vertex.x, vertex.y);
                for (var i = 1, len = bezierPoints.length; i < len; i++) {
                    vertex = bezierPoints[i].getVertex();
                    backward = bezierPoints[i].getBackward();
                    forward = bezierPoints[i - 1].getForward();
                    drawer.bezierTo(forward.x, forward.y, backward.x, backward.y, vertex.x, vertex.y);
                }
                return this;
            }
        });
    }
};

//src/graphic/bezierpoint.js
/**
 * @fileOverview
 *
 * 表示一个贝塞尔点
 */
_p[24] = {
    value: function(require, exports, module) {
        var ShapePoint = _p.r(63);
        var Vector = _p.r(73);
        /**
     * @class kity.BezierPoint
     *
     * @description 表示一个贝塞尔点
     *              一个贝塞尔点由顶点坐标（曲线经过的点）、前方控制点、后方控制点表示
     */
        var BezierPoint = _p.r(11).createClass("BezierPoint", {
            /**
         * @constructor
         * @for kity.BezierPoint
         *
         * @description 创建一个具有默认顶点坐标的贝塞尔点，两个控制点的坐标和顶点一致
         *
         * @param  {Number}  x        顶点的 x 坐标
         * @param  {Number}  y        顶点的 y 坐标
         * @param  {Boolean} isSmooth 指示当前贝塞尔点是否光滑，光滑会约束顶点和两个控制点共线
         */
            constructor: function(x, y, isSmooth) {
                //顶点
                this.vertex = new ShapePoint(x, y);
                //控制点
                this.forward = new ShapePoint(x, y);
                this.backward = new ShapePoint(x, y);
                //是否平滑
                this.setSmooth(isSmooth === undefined || isSmooth);
                this.setSymReflaction(true);
            },
            /**
         * @method clone()
         * @for kity.BezierPoint
         * @description 返回贝塞尔点的一份拷贝
         *
         * @grammar clone() => {kity.BezierPoint}
         */
            clone: function() {
                var newPoint = new BezierPoint(), tmp = null;
                tmp = this.getVertex();
                newPoint.setVertex(tmp.x, tmp.y);
                tmp = this.getForward();
                newPoint.setForward(tmp.x, tmp.y);
                tmp = this.getBackward();
                newPoint.setBackward(tmp.x, tmp.y);
                newPoint.setSymReflaction(this.isSymReflaction);
                newPoint.setSmooth(this.isSmooth());
                return newPoint;
            },
            /**
         * @method setVertex()
         * @for kity.BezierPoint
         * @description 设置贝塞尔点的顶点坐标，注意，控制点的坐标不会跟着变化。希望控制点的坐标跟着变化，请用 moveTo() 方法
         *
         * @grammar setVertex(x, y) => {this}
         *
         * @param {Number} x 顶点的 x 坐标
         * @param {Number} y 顶点的 y 坐标
         */
            setVertex: function(x, y) {
                this.vertex.setPoint(x, y);
                this.update();
                return this;
            },
            /**
         * @method moveTo()
         * @for kity.BezierPoint
         * @description 同步移动整个贝塞尔点，使顶点的移动到指定的坐标中。控制点的位置相对顶点坐标固定。
         *
         * @grammar moveTo() => {this}
         *
         * @param  {Number} x 顶点的目标 x 坐标
         * @param  {Number} y 顶点的目标 y 坐标
         *
         */
            moveTo: function(x, y) {
                var oldForward = this.forward.getPoint(), oldBackward = this.backward.getPoint(), oldVertex = this.vertex.getPoint(), //移动距离
                distance = {
                    left: x - oldVertex.x,
                    top: y - oldVertex.y
                };
                // 更新
                this.forward.setPoint(oldForward.x + distance.left, oldForward.y + distance.top);
                this.backward.setPoint(oldBackward.x + distance.left, oldBackward.y + distance.top);
                this.vertex.setPoint(x, y);
                this.update();
            },
            /**
         * @method setForward()
         * @for kity.BezierPoint
         * @description 设置前方控制点的位置，如果贝塞尔点光滑，后方控制点会跟着联动
         *
         * @grammar setForward(x, y) => {this}
         *
         * @param {Number} x 前方控制点的 x 坐标
         * @param {Number} y 前方控制点的 y 坐标
         */
            setForward: function(x, y) {
                this.forward.setPoint(x, y);
                //更新后置点
                if (this.smooth) {
                    this.updateAnother(this.forward, this.backward);
                }
                this.update();
                this.lastControlPointSet = this.forward;
                return this;
            },
            /**
         * @method setBackward()
         * @for kity.BezierPoint
         * @description 设置后方控制点的位置，如果贝塞尔点光滑，前方控制点会跟着联动
         *
         * @grammar setBackward(x, y) => {this}
         *
         * @param {Number} x 后方控制点的 x 坐标
         * @param {Number} y 后方控制点的 y 坐标
         */
            setBackward: function(x, y) {
                this.backward.setPoint(x, y);
                //更新前置点
                if (this.smooth) {
                    this.updateAnother(this.backward, this.forward);
                }
                this.update();
                this.lastControlPointSet = this.backward;
                return this;
            },
            /**
         * @method setSymReflaction()
         * @for kity.BezierPoint
         * @description 设定是否镜像两个控制点的位置
         *
         * @grammar setSymReflaction(value) => {this}
         *
         * @param {boolean} value 如果设置为 true，且贝塞尔点光滑，两个控制点离顶点的距离相等
         */
            setSymReflaction: function(value) {
                this.symReflaction = value;
                if (this.smooth) this.setSmooth(true);
                return this;
            },
            /**
         * @method isSymReflaction()
         * @for kity.BezierPoint
         * @description 当前贝塞尔点的两个控制点是否被镜像约束
         *
         * @grammar isSymReflaction() => {boolean}
         */
            isSymReflaction: function() {
                return this.symReflaction;
            },
            /**
         * @private
         *
         * 根据前方控制点或后方控制点更新另一方
         */
            updateAnother: function(p, q) {
                var v = this.getVertex(), pv = Vector.fromPoints(p.getPoint(), v), vq = Vector.fromPoints(v, q.getPoint());
                vq = pv.normalize(this.isSymReflaction() ? pv.length() : vq.length());
                q.setPoint(v.x + vq.x, v.y + vq.y);
                return this;
            },
            /**
         * @method setSmooth()
         * @for kity.BezierPoint
         * @description 设置贝塞尔点是否光滑，光滑会约束顶点和两个控制点共线
         *
         * @param {Boolean} isSmooth 设置为 true 让贝塞尔点光滑
         */
            setSmooth: function(isSmooth) {
                var lc;
                this.smooth = !!isSmooth;
                if (this.smooth && (lc = this.lastControlPointSet)) {
                    this.updateAnother(lc, lc == this.forward ? this.backward : this.forward);
                }
                return this;
            },
            /**
         * @method isSmooth()
         * @for kity.BezierPoint
         * @description 判断贝塞尔点是否光滑
         *
         * @grammar isSmooth() => {boolean}
         */
            isSmooth: function() {
                return this.smooth;
            },
            /**
         * @method getVertex()
         * @for kity.BezierPoint
         * @description 获得当前贝塞尔点的顶点
         *
         * @grammar getVertex() => {kity.ShapePoint}
         */
            getVertex: function() {
                return this.vertex.getPoint();
            },
            /**
         * @method getForward()
         * @for kity.BezierPoint
         * @description 获得当前贝塞尔点的前方控制点
         *
         * @grammar getForward() => {kity.ShapePoint}
         */
            getForward: function() {
                return this.forward.getPoint();
            },
            /**
         * @method getBackward()
         * @for kity.BezierPoint
         * @description 获得当前贝塞尔点的后方控制点
         *
         * @grammar getBackward() => {kity.ShapePoint}
         */
            getBackward: function() {
                return this.backward.getPoint();
            },
            /**
         * @private
         *
         * 联动更新相关的贝塞尔曲线
         */
            update: function() {
                if (!this.container) {
                    return this;
                }
                //新增参数 this， 把当前引起变化的点传递过去， 以便有需要的地方可以获取到引起变化的源
                if (this.container.update) this.container.update(this);
            }
        });
        return BezierPoint;
    }
};

//src/graphic/box.js
/**
 * @fileOverview
 *
 * 表示一个矩形区域
 */
_p[25] = {
    value: function(require, exports, module) {
        /**
     * @class kity.Box
     * @description 表示一个矩形区域
     */
        var Box = _p.r(11).createClass("Box", {
            /**
         * @constructor
         * @for kity.Box
         *
         * @grammar new kity.Box(x, y, width, height)
         * @grammar new kity.Box(box)
         *
         * @param  {Number} x|box.x      矩形区域的 x 坐标
         * @param  {Number} y|box.y      矩形区域的 y 坐标
         * @param  {Number} width|box.width   矩形区域的宽度
         * @param  {Number} height|box.height 矩形区域的高度
         *
         * @example
         *
         * ```js
         * var box = new kity.Box(10, 20, 50, 50);
         * var box2 = new kity.Box({x: 10, y: 20, width: 50, height: 50});
         * ```
         */
            constructor: function(x, y, width, height) {
                var box = arguments[0];
                if (box && typeof box === "object") {
                    x = box.x;
                    y = box.y;
                    width = box.width;
                    height = box.height;
                }
                if (width < 0) {
                    x -= width = -width;
                }
                if (height < 0) {
                    y -= height = -height;
                }
                /**
             * @property x
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的 x 坐标
             */
                this.x = x || 0;
                /**
             * @property y
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的 y 坐标
             */
                this.y = y || 0;
                /**
             * @property width
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的宽度
             */
                this.width = width || 0;
                /**
             * @property height
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的高度
             */
                this.height = height || 0;
                /**
             * @property left
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的最左侧坐标，等价于 x 的值
             */
                this.left = this.x;
                /**
             * @property right
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的最右侧坐标，等价于 x + width 的值
             */
                this.right = this.x + this.width;
                /**
             * @property top
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的最上侧坐标，等价于 y 的值
             */
                this.top = this.y;
                /**
             * @property bottom
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的最下侧坐标，等价于 y + height 的值
             */
                this.bottom = this.y + this.height;
                /**
             * @property cx
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的中心 x 坐标
             */
                this.cx = this.x + this.width / 2;
                /**
             * @property cy
             * @for kity.Box
             * @type {Number}
             * @readOnly
             * @description 矩形区域的中心 y 坐标
             */
                this.cy = this.y + this.height / 2;
            },
            /**
         * @method getRangeX()
         * @for kity.Box
         * @description 获得矩形区域的 x 值域
         *
         * @grammar getRangeX() => {Number[]}
         *
         * @example
         *
         * var box = new kity.Box(10, 10, 30, 50);
         * console.log(box.getRangeX()); // [10, 40]
         */
            getRangeX: function() {
                return [ this.left, this.right ];
            },
            /**
         * @method getRangeY()
         * @for kity.Box
         * @description 获得矩形区域的 y 值域
         *
         * @grammar getRangeY() => {Number[]}
         *
         * @example
         *
         * var box = new kity.Box(10, 10, 30, 50);
         * console.log(box.getRangeY()); // [10, 60]
         */
            getRangeY: function() {
                return [ this.top, this.bottom ];
            },
            /**
         * @method merge()
         * @for kity.Box
         * @description 把当前矩形区域和指定的矩形区域合并，返回一个新的矩形区域（即包含两个源矩形区域的最小矩形区域）
         *
         * @grammar merge(another) => {kity.Box}
         * @param  {kity.Box} another 要合并的矩形区域
         *
         * @example
         *
         * ```js
         * var box1 = new kity.Box(10, 10, 50, 50);
         * var box2 = new kity.Box(30, 30, 50, 50);
         * var box3 = box1.merge(box2);
         * console.log(box3.valueOf()); // [10, 10, 70, 70]
         * ```
         */
            merge: function(another) {
                if (this.isEmpty()) {
                    return new Box(another.x, another.y, another.width, another.height);
                }
                var left = Math.min(this.left, another.left), right = Math.max(this.right, another.right), top = Math.min(this.top, another.top), bottom = Math.max(this.bottom, another.bottom);
                return new Box(left, top, right - left, bottom - top);
            },
            /**
         * @method intersect()
         * @for kity.Box
         * @description 求当前矩形区域和指定的矩形区域重叠的矩形区域
         *
         * @grammar intersect(another) => {kity.Box}
         * @param  {kity.Box} another 要求重叠的矩形区域
         *
         * @example
         *
         * ```js
         * var box1 = new kity.Box(10, 10, 50, 50);
         * var box2 = new kity.Box(30, 30, 50, 50);
         * var box3 = box1.intersect(box2);
         * console.log(box3.valueOf()); // [30, 30, 20, 20]
         * ```
         */
            intersect: function(another) {
                if (!another instanceof Box) {
                    another = new Box(another);
                }
                var left = Math.max(this.left, another.left), right = Math.min(this.right, another.right), top = Math.max(this.top, another.top), bottom = Math.min(this.bottom, another.bottom);
                if (left > right || top > bottom) return new Box();
                return new Box(left, top, right - left, bottom - top);
            },
            /**
         * @method expand()
         * @for kity.Box
         * @description 扩展（或收缩）当前的盒子，返回新的盒子
         *
         * @param {Number} top
         *     矩形区域的上边界往上扩展的值；如果是负数，则上边界往下收缩
         *
         * @param {Number} right
         *     [Optional] 矩形区域的右边界往右拓展的值；
         *                如果是负数，则右边界往左收缩；
         *                如果不设置该值，使用和 top 同样的值。
         *
         * @param {Number} bottom
         *     [Optional] 矩形区域的下边界往下拓展的值；
         *                如果是负数，则下边界往上收缩；
         *                如果不设置该值，使用和 top 同样的值。
         *
         * @param {Number} left
         *     [Optional] 矩形区域的左边界往左拓展的值;
         *                如果是负数，则左边界往右收缩;
         *                如果不设置该值，使用和 right 同样的值。
         *
         * @example
         *
         * ```js
         * var box = new kity.Box(10, 10, 20, 20);
         * var box1 = box.expand(10); // [0, 0, 40, 40]
         * var box2 = box.expand(10, 20); // [0, -10, 40, 60]
         * var box3 = box.expand(1, 2, 3, 4); // [9, 8, 24, 26]
         * ```
         */
            expand: function(top, right, bottom, left) {
                if (arguments.length < 1) {
                    return new Box(this);
                }
                if (arguments.length < 2) {
                    right = top;
                }
                if (arguments.length < 3) {
                    bottom = top;
                }
                if (arguments.length < 4) {
                    left = right;
                }
                var x = this.left - left, y = this.top - top, width = this.width + right, height = this.height + top;
                return new Box(x, y, width, height);
            },
            /**
         * @method valueOf()
         * @for kity.Box
         * @description 返回当前盒子的数组表示
         *
         * @grammar valueOf() => {Number[]}
         *
         * @example
         *
         * ```js
         * var box = new kity.Box(0, 0, 200, 50);
         * console.log(box.valueOf()); // [0, 0, 200, 50]
         * ```
         */
            valueOf: function() {
                return [ this.x, this.y, this.width, this.height ];
            },
            /**
         * @method toString()
         * @for kity.Box
         * @description 返回当前盒子的字符串表示
         *
         * @grammar toString() => {String}
         *
         * @example
         *
         * ```js
         * var box = new kity.Box(0, 0, 200, 50);
         * console.log(box.toString()); // "0 0 200 50"
         */
            toString: function() {
                return this.valueOf().join(" ");
            },
            /**
         * @method isEmpty()
         * @for kity.Box
         * @description 判断当前盒子是否具有尺寸（面积大
         *
         * @grammar isEmpty() => {boolean}
         *
         * @example
         * ```js
         * var box = new kity.Box(0, 0, 0, 100000);
         * console.log(box.isEmpty()); // true
         * ```
         */
            isEmpty: function() {
                return !this.width || !this.height;
            }
        });
        /**
     * @method parse()
     * @static
     * @for kity.Box
     * @description 解析一个字符串或数组为 kity.Box 对象
     *
     * @grammar kity.Box.parse(any) => {kity.Box}
     *
     * @param  {Number[]|String} any 要解析的字符串或数组
     *
     * @example
     *
     * ```js
     * console.log(kity.Box.parse('0 0 100 200'));
     * console.log(kity.Box.parse([0, 0, 100, 200]));
     * ```
     */
        Box.parse = function(any) {
            if (typeof any == "string") {
                return Box.parse(any.split(/[\s,]+/).map(parseFloat));
            }
            if (any instanceof Array) {
                return new Box(any[0], any[1], any[2], any[3]);
            }
            if ("x" in any) return new Box(any);
            return null;
        };
        return Box;
    }
};

//src/graphic/circle.js
/**
 * @fileOverview
 *
 * 绘制和使用圆形
 */
_p[26] = {
    value: function(require, exports, module) {
        /**
     * @class kity.Circle
     * @base kity.Ellipse
     * @description 表示一个圆形
     */
        return _p.r(11).createClass("Circle", {
            base: _p.r(32),
            /**
         * @constructor
         * @for kity.Circle
         *
         * @grammar new kity.Circle(radius, cx, cy)
         *
         * @param  {Number} radius 半径
         * @param  {Number} cx     圆心 x 坐标
         * @param  {Number} cy     圆心 y 坐标
         */
            constructor: function(radius, cx, cy) {
                this.callBase(radius, radius, cx, cy);
            },
            /**
         * @method
         * @for kity.Circle
         * @description 获取圆形的半径
         *
         * @grammar getRadius() => {Number}
         */
            getRadius: function() {
                return this.getRadiusX();
            },
            /**
         * @method
         * @for kity.Circle
         * @description 设置圆形的半径
         *
         * @grammar setRadius() => {this}
         *
         * @param {Number} radius 半径大小
         */
            setRadius: function(radius) {
                return this.callBase(radius, radius);
            }
        });
    }
};

//src/graphic/clip.js
/**
 * @fileOverview
 *
 * 支持图形裁切
 */
_p[27] = {
    value: function(require, exports, module) {
        var Class = _p.r(11);
        var Shape = _p.r(60);
        /**
     * @class kity.Clip
     * @base kity.Resource
     * @mixins kity.ShapeContainer
     *
     * @description 创建图形裁切，用于裁切目标图形
     */
        var Clip = Class.createClass("Clip", {
            base: Shape,
            mixins: [ _p.r(61) ],
            /**
         * @constructor
         * @for kity.Clip
         *
         * @grammar new kity.Clip(paper)
         *
         * @param  {kity.Paper} paper 资源所属的文档
         *
         * @example
         *
         * ```js
         * var circle = paper.put(new kity.Circle(100).fill('yellow'));
         * var clip = new kity.Clip(paper);
         * clip.addShape(new kity.Circle(100, 50, 0));
         * clip.clip(circle);
         * ```
         */
            constructor: function(paper) {
                this.callBase("clipPath", paper);
            },
            /**
         * @method clip()
         * @for kity.Clip
         * @description 将裁切应用到指定的图形上，应用之后，目标图形将显示与裁切形状重合的部分
         *
         * @grammar clip(shape) => {this}
         *
         * @param  {kity.Shape} shape 要应用裁切的图形
         */
            clip: function(shape) {
                shape.getNode().setAttribute("clip-path", this);
                return this;
            }
        });
        Class.extendClass(Shape, {
            /**
         * @method clipWidth()
         * @for kity.Shape
         *
         * @grammar clipWidth(clip) => {this}
         *
         * @param {kity.Clip|kity.Shape} clip 要用于裁切当前图形的图形；
         *     如果 clip 本身是一个裁切对象（kity.Clip 类型），则直接裁切；
         *     否则将创建新的裁切包含给定的图形，然后对当前图形进行裁切
         *
         * @example
         *
         * ```js
         * var circle = paper.put(new kity.Circle(100));
         * var rect = paper.put(new kity.Rect(100, 100, -100, 0));
         *
         * circle.clipWidth(rect);
         * ```
         */
            clipWith: function(clip) {
                if (clip instanceof Shape) {
                    clip = new Clip(clip.getPaper()).addShape(clip);
                }
                clip.clip(this);
                return this;
            }
        });
        return Clip;
    }
};

//src/graphic/color.js
/**
 * @fileOverview
 *
 * 提供颜色支持
 */
_p[28] = {
    value: function(require, exports, module) {
        var Utils = _p.r(12), StandardColor = _p.r(64), ColorUtils = {}, /**
         * @class kity.Color
         * @description 表示一个颜色
         */
        Color = _p.r(11).createClass("Color", {
            /**
             * @constructor
             * @for kity.Color
             *
             * @grammar new kity.Color(r, g, b)
             * @grammar new kity.Color(r, g, b, a)
             * @grammar new kity.Color(colorString)
             *
             * @param {Number} r 红色分量，取值 0 - 255
             * @param {Number} g 绿色分量，取值 0 - 255
             * @param {Number} b 蓝色分量，取值 0 - 255
             * @param {Number} a 透明度（可选），取值 0 - 100
             * @param {String} colorString 一个代表颜色的字符串，可以是：
             *     熟知颜色表：如 'red', 'yellow'
             *     HEX 表示：如 '#368', '#123456'
             *     RGB 表示：如 'RGB(200, 200, 0)', 'RGBA(200, 200, 200, .5)'
             *     HSL 表示：如 'HSL(100, 60%, 80%)', 'HSLA(100, 60%, 80%, .5)'
             */
            constructor: function() {
                var colorValue = null;
                //parse构造
                if (typeof arguments[0] === "string") {
                    colorValue = ColorUtils.parseToValue(arguments[0]);
                    //解析失败
                    if (colorValue === null) {
                        colorValue = {
                            r: 0,
                            g: 0,
                            b: 0,
                            h: 0,
                            s: 0,
                            l: 0,
                            a: 1
                        };
                    }
                } else {
                    colorValue = {
                        r: arguments[0] | 0,
                        g: arguments[1] | 0,
                        b: arguments[2] | 0,
                        //alpha 默认为1
                        a: arguments[3] === undefined ? 1 : parseFloat(arguments[3])
                    };
                    colorValue = ColorUtils.overflowFormat(colorValue);
                    //获取hsl分量
                    colorValue = Utils.extend(colorValue, ColorUtils.rgbValueToHslValue(colorValue));
                }
                this._color = colorValue;
            },
            /**
             * @method set()
             * @for kity.Color
             *
             * @description 设置当前颜色某个分量的值
             *
             * @grammar set(name, value) => {this}
             *
             * @param {string} name  要设置的颜色通道的名称
             *     r: 红色（Red），取值范围 [0, 255]
             *     g: 绿色（Green），取值范围 [0, 255]
             *     b: 蓝色（Blue），取值范围 [0, 255]
             *     a: 透明度（Alpha），取值范围 [0, 1]
             *     h: 色环角度（Hue），取值范围 [0, 359]
             *     s: 饱和度（Saturation），取值范围 [0, 100]
             *     l: 亮度（Lightness），取值范围 [0, 100]
             *     r、g、b 值和 h、s、l 值会联动修改
             * @param {number} value 要设置的值
             */
            set: function(name, value) {
                var values = null;
                //设置的值非法
                if (!Color._MAX_VALUE[name]) {
                    throw new Error("Color set(): Illegal parameter");
                }
                if (name !== "a") {
                    value = Math.floor(value);
                }
                if (name == "h") {
                    value = (value + 360) % 360;
                }
                this._color[name] = Math.max(Color._MIN_VALUE[name], Math.min(Color._MAX_VALUE[name], value));
                if ("rgb".indexOf(name) !== -1) {
                    this._color = Utils.extend(this._color, ColorUtils.rgbValueToHslValue(this._color));
                } else if ("hsl".indexOf(name) !== -1) {
                    this._color = Utils.extend(this._color, ColorUtils.hslValueToRGBValue(this._color));
                }
                return this;
            },
            /**
             * @method inc()
             *
             * @description 返回新的颜色，表示当前颜色增加指定通道的值之后的颜色
             *
             * @grammar inc(name, value) => {this}
             *
             * @param  {string} name  要增加的通道的名称，具体含义请查看 set 方法
             * @param  {number} value 增量值
             */
            inc: function(name, value) {
                value = this.get(name) + value;
                if (name == "h") {
                    value = (value + 360) % 360;
                } else {
                    value = Math.min(Color._MAX_VALUE[name], value);
                    value = Math.max(Color._MIN_VALUE[name], value);
                }
                return this.clone().set(name, value);
            },
            /**
             * @method dec()
             * @for kity.Color
             *
             * @description 返回新的颜色，表示当前颜色减少指定通道的值之后的颜色
             *
             * @grammar dec(name, value) => {this}
             *
             * @param  {string} name  要减少值的通道的名称，具体含义请查看 set 方法
             * @param  {number} value 减量值
             */
            dec: function(name, value) {
                return this.inc(name, -value);
            },
            /**
             * @method clone()
             * @for kity.Color
             *
             * @description 返回当前颜色的一个拷贝
             *
             * @grammar clone() => {kity.Color}
             */
            clone: function() {
                return new Color(this.toRGBA());
            },
            /**
             * @method get()
             * @for kity.Color
             *
             * @description 返回当前颜色指定的分量
             *
             * @grammar get() => {number}
             */
            get: function(name) {
                if (!Color._MAX_VALUE[name]) {
                    return null;
                }
                return this._color[name];
            },
            getValues: function() {
                return Utils.clone(this._color);
            },
            /**
             * @method valueOf()
             * @for kity.Color
             *
             * @description 返回当前颜色的一个字面量表示
             *
             * @return {plain} 颜色字面量，其结构为：
             *     {
             *         r: 0,
             *         g: 0,
             *         b: 0,
             *         a: 0,
             *         h: 0,
             *         s: 0,
             *         l: 0
             *     }
             */
            valueOf: function() {
                return this.getValues();
            },
            /**
             * @method toRGB()
             * @for kity.Color
             *
             * @description 返回当前颜色的 RGB 表示，如果颜色有透明度，将抛弃透明度属性（想要保留请使用 toRGBA()）方法。
             *
             * @grammar toRGB() => {string}
             */
            toRGB: function() {
                return ColorUtils.toString(this._color, "rgb");
            },
            /**
             * @method toRGBA()
             * @for kity.Color
             *
             * @description 返回当前颜色的 RGBA 表示
             *
             * @grammar toRGBA() => {string}
             */
            toRGBA: function() {
                return ColorUtils.toString(this._color, "rgba");
            },
            /**
             * @method toHEX()
             * @for kity.Color
             *
             * @description 返回当前颜色的 HEX 表示，如果颜色有透明度，将抛弃透明度属性（想要保留请使用 toRGBA()）方法。
             *
             * @grammar toHEX() => {string}
             */
            toHEX: function() {
                return ColorUtils.toString(this._color, "hex");
            },
            /**
             * @method toHSL()
             * @for kity.Color
             *
             * @description 返回当前颜色的 HSL 表示，如果颜色有透明度，将抛弃透明度属性（想要保留请使用 toHSLA()）方法。
             *
             * @grammar toHSL() => {string}
             */
            toHSL: function() {
                return ColorUtils.toString(this._color, "hsl");
            },
            /**
             * @method toHSLA()
             * @for kity.Color
             *
             * @description 返回当前颜色的 HSLA 表示
             *
             * @grammar toHSLA() => {string}
             */
            toHSLA: function() {
                return ColorUtils.toString(this._color, "hsla");
            },
            /**
             * @method toString()
             * @for kity.Color
             *
             * @description 返回当前颜色的 RGB 或 RGBA 表示，如果颜色有透明度，将使用 RGBA 形式，否则是 RGB 形式
             * @grammar toString() => {string}
             */
            toString: function() {
                if (this._color.a === 1) {
                    return this.toRGB();
                }
                return this.toRGBA();
            }
        });
        //Color 静态方法
        Utils.extend(Color, {
            //各分量可表示的最大值
            _MAX_VALUE: {
                r: 255,
                g: 255,
                b: 255,
                h: 360,
                s: 100,
                l: 100,
                a: 1
            },
            //各分量最小值
            _MIN_VALUE: {
                r: 0,
                g: 0,
                b: 0,
                h: 0,
                s: 0,
                l: 0,
                a: 0
            },
            //分量常量
            R: "r",
            G: "g",
            B: "b",
            H: "h",
            S: "s",
            L: "l",
            A: "a",
            /**
         * @method parse()
         * @static
         * @for kity.Color
         *
         * @description 解析一个颜色字符串为 kity.Color 对象
         *
         * @grammar kity.Color.parse(valStr)
         *
         * @param  {string} valStr 一个代表颜色的字符串，可以是：
         *     熟知颜色表：如 'red', 'yellow'
         *     HEX 表示：如 '#368', '#123456'
         *     RGB 表示：如 'RGB(200, 200, 0)', 'RGBA(200, 200, 200, .5)'
         *     HSL 表示：如 'HSL(100, 60%, 80%)', 'HSLA(100, 60%, 80%, .5)'
         */
            parse: function(valStr) {
                var rgbValue;
                if (Utils.isString(valStr)) {
                    rgbValue = ColorUtils.parseToValue(valStr);
                }
                if (Utils.isObject(valStr) && "r" in valStr) {
                    rgbValue = valStr;
                }
                //解析失败， 返回一个默认color实例
                if (rgbValue === null) {
                    return new Color();
                }
                return new Color(rgbValue.r, rgbValue.g, rgbValue.b, rgbValue.a);
            },
            /**
         * @method createHSL()
         * @for kity.Color
         * @static
         *
         * @description 创建一个 HSL 颜色
         *
         * @grammar kity.Color.createHSL(h, s, l) => {kity.Color}
         *
         * @param  {number} h 色环（Hue）分量值，取值范围 [0, 359]
         * @param  {number} s 饱和度（Saturation）分量值，取值范围 [0, 100]
         * @param  {number} l 亮度（Lighteness）分量值，取值范围 [0, 100]
         */
            createHSL: function(h, s, l) {
                return Color.createHSLA(h, s, l, 1);
            },
            /**
         * @method createHSLA()
         * @for kity.Color
         * @static
         *
         * @description 创建一个 HSLA 颜色
         *
         * @grammar kity.Color.createHSLA(h, s, l, a) => {kity.Color}
         *
         * @param  {number} h 色环（Hue）分量值，取值范围 [0, 359]
         * @param  {number} s 饱和度（Saturation）分量值，取值范围 [0, 100]
         * @param  {number} l 亮度（Lighteness）分量值，取值范围 [0, 100]
         * @param  {number} a 透明度（Alpha）分量值，取值范围 [0, 1]
         */
            createHSLA: function(h, s, l, a) {
                var colorValue = null;
                s += "%";
                l += "%";
                colorValue = [ "hsla(" + h, s, l, a + ")" ];
                return Color.parse(colorValue.join(", "));
            },
            /**
         * @method createRGB()
         * @for kity.Color
         * @static
         *
         * @description 创建一个 RGB 颜色
         *
         * @grammar kity.Color.createRGB(r, g, b) => {kity.Color}
         *
         * @param  {number} r 红色（Red）分量值，取值范围 [0, 255]
         * @param  {number} g 绿色（Green）分量值，取值范围 [0, 255]
         * @param  {number} b 蓝色（Blue）分量值，取值范围 [0, 255]
         */
            createRGB: function(r, g, b) {
                return Color.createRGBA(r, g, b, 1);
            },
            /**
         * @method createRGBA()
         * @for kity.Color
         * @static
         *
         * @description 创建一个 RGBA 颜色
         *
         * @grammar kity.Color.createRGBA(r, g, b, a) => {kity.Color}
         *
         * @param  {number} r 红色（Red）分量值，取值范围 [0, 255]
         * @param  {number} g 绿色（Green）分量值，取值范围 [0, 255]
         * @param  {number} b 蓝色（Blue）分量值，取值范围 [0, 255]
         * @param  {number} a 透明度（Alpha）分量值，取值范围 [0, 1]
         */
            createRGBA: function(r, g, b, a) {
                return new Color(r, g, b, a);
            }
        });
        //内部工具对象
        Utils.extend(ColorUtils, {
            parseToValue: function(valStr) {
                var rgbaValue = {};
                /* 优先检测在调色板中是否有对应的颜色 */
                valStr = StandardColor.EXTEND_STANDARD[valStr] || StandardColor.COLOR_STANDARD[valStr] || valStr;
                /* 颜色转换 */
                //hex格式
                if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(valStr)) {
                    rgbaValue = ColorUtils.hexToValue(valStr);
                } else if (/^(rgba?)/i.test(valStr)) {
                    rgbaValue = ColorUtils.rgbaToValue(valStr);
                } else if (/^(hsla?)/i.test(valStr)) {
                    rgbaValue = ColorUtils.hslaToValue(valStr);
                } else {
                    return null;
                }
                return ColorUtils.overflowFormat(rgbaValue);
            },
            hexToValue: function(hexStr) {
                var result = {}, keys = [ "r", "g", "b" ];
                if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(hexStr)) {
                    hexStr = RegExp.$1.split("");
                    Utils.each(keys, function(key, index) {
                        if (hexStr.length === 3) {
                            result[key] = ColorUtils.toNumber(hexStr[index] + hexStr[index]);
                        } else {
                            result[key] = ColorUtils.toNumber(hexStr[index * 2] + hexStr[index * 2 + 1]);
                        }
                    });
                    //转换出hsl值
                    result = Utils.extend(result, ColorUtils.rgbValueToHslValue(result));
                    result.a = 1;
                    return result;
                }
                return null;
            },
            rgbaToValue: function(rgbaStr) {
                var result = {}, hasAlpha = false, keys = [ "r", "g", "b" ];
                if (/^(rgba?)/i.test(rgbaStr)) {
                    hasAlpha = RegExp.$1.length === 4;
                    rgbaStr = rgbaStr.replace(/^rgba?/i, "").replace(/\s+/g, "").replace(/[^0-9,.]/g, "").split(",");
                    Utils.each(keys, function(key, index) {
                        result[key] = rgbaStr[index] | 0;
                    });
                    //转换出hsl值
                    result = Utils.extend(result, ColorUtils.rgbValueToHslValue(result));
                    result.a = hasAlpha ? parseFloat(rgbaStr[3]) : 1;
                    return result;
                }
                return null;
            },
            hslaToValue: function(hslaStr) {
                var result = {}, hasAlpha = false;
                if (/^(hsla?)/i.test(hslaStr)) {
                    hasAlpha = RegExp.$1.length === 4;
                    hslaStr = hslaStr.replace(/^hsla?/i, "").replace(/\s+/g, "").replace(/[^0-9,.]/g, "").split(",");
                    //记录hsl值
                    result.h = hslaStr[0] | 0;
                    result.s = hslaStr[1] | 0;
                    result.l = hslaStr[2] | 0;
                    //转换出rgb值
                    result = Utils.extend(result, ColorUtils.hslValueToRGBValue(result));
                    //hsl值转换为rgb值
                    result = ColorUtils.hslValueToRGBValue(result);
                    result.a = hasAlpha ? parseFloat(hslaStr[3]) : 1;
                    return result;
                }
                return null;
            },
            //hsl值对象转换为rgb值对象
            hslValueToRGBValue: function(hslValue) {
                function trans(v1, v2, vH) {
                    if (vH < 0) {
                        vH += 1;
                    } else if (vH > 1) {
                        vH -= 1;
                    }
                    if (6 * vH < 1) {
                        return v1 + (v2 - v1) * 6 * vH;
                    } else if (2 * vH < 1) {
                        return v2;
                    } else if (3 * vH < 2) {
                        return v1 + (v2 - v1) * ((2 / 3 - vH) * 6);
                    }
                    return v1;
                }
                var q = null, p = null, result = {};
                hslValue = Utils.extend({}, hslValue);
                hslValue.h = hslValue.h / 360;
                hslValue.s = hslValue.s / 100;
                hslValue.l = hslValue.l / 100;
                //分量计算
                if (hslValue.s === 0) {
                    result.r = result.g = result.b = hslValue.l;
                } else {
                    if (hslValue.l < .5) {
                        q = hslValue.l * (1 + hslValue.s);
                    } else {
                        q = hslValue.l + hslValue.s - hslValue.l * hslValue.s;
                    }
                    p = 2 * hslValue.l - q;
                    result.r = trans(p, q, hslValue.h + 1 / 3);
                    result.g = trans(p, q, hslValue.h);
                    result.b = trans(p, q, hslValue.h - 1 / 3);
                }
                result.r = Math.min(Math.round(result.r * 255), 255);
                result.g = Math.min(Math.round(result.g * 255), 255);
                result.b = Math.min(Math.round(result.b * 255), 255);
                return result;
            },
            //rgb值对象转换为hsl值对象
            rgbValueToHslValue: function(rgbValue) {
                var max = null, min = null, result = {};
                rgbValue = Utils.extend({}, rgbValue);
                rgbValue.r = rgbValue.r / 255;
                rgbValue.g = rgbValue.g / 255;
                rgbValue.b = rgbValue.b / 255;
                max = Math.max(rgbValue.r, rgbValue.g, rgbValue.b);
                min = Math.min(rgbValue.r, rgbValue.g, rgbValue.b);
                //h分量计算
                if (max === min) {
                    result.h = 0;
                } else if (max === rgbValue.r) {
                    if (rgbValue.g >= rgbValue.b) {
                        result.h = 60 * (rgbValue.g - rgbValue.b) / (max - min);
                    } else {
                        result.h = 60 * (rgbValue.g - rgbValue.b) / (max - min) + 360;
                    }
                } else if (max === rgbValue.g) {
                    result.h = 60 * (rgbValue.b - rgbValue.r) / (max - min) + 120;
                } else if (max === rgbValue.b) {
                    result.h = 60 * (rgbValue.r - rgbValue.g) / (max - min) + 240;
                }
                //l分量计算
                result.l = (max + min) / 2;
                //s分量计算
                if (result.l === 0 || max === min) {
                    result.s = 0;
                } else if (result.l > 0 && result.l <= .5) {
                    result.s = (max - min) / (max + min);
                } else {
                    result.s = (max - min) / (2 - max - min);
                }
                //格式化hsl结果
                result.h = Math.round(result.h);
                result.s = Math.round(result.s * 100);
                result.l = Math.round(result.l * 100);
                return result;
            },
            toString: function(colorValue, type) {
                var vals = [];
                colorValue = Utils.extend({}, colorValue);
                if (type.indexOf("hsl") !== -1) {
                    colorValue.s += "%";
                    colorValue.l += "%";
                }
                if (type !== "hex") {
                    Utils.each(type.split(""), function(key) {
                        vals.push(colorValue[key]);
                    });
                    return (type + "(" + vals.join(", ") + ")").toLowerCase();
                } else {
                    vals.push(ColorUtils.toHexValue(+colorValue.r));
                    vals.push(ColorUtils.toHexValue(+colorValue.g));
                    vals.push(ColorUtils.toHexValue(+colorValue.b));
                    return ("#" + vals.join("")).toLowerCase();
                }
            },
            //16进制的2个数字转化为10进制， 如果转化失败， 返回0
            toNumber: function(value) {
                return Number("0x" + value) | 0;
            },
            toHexValue: function(value) {
                var result = value.toString(16);
                return result.length === 1 ? "0" + result : result;
            },
            //溢出控制
            overflowFormat: function(value) {
                var tmpValue = Utils.extend({}, value), keys = "rgba";
                Utils.each(keys.split(""), function(key) {
                    if (!tmpValue.hasOwnProperty(key)) {
                        return;
                    }
                    //上溢出
                    tmpValue[key] = Math.min(Color._MAX_VALUE[key], tmpValue[key]);
                    //下溢出
                    tmpValue[key] = Math.max(Color._MIN_VALUE[key], tmpValue[key]);
                });
                return tmpValue;
            }
        });
        return Color;
    }
};

//src/graphic/container.js
_p[29] = {
    value: function(require, exports, module) {
        function itemRemove() {
            this.container.removeItem(this);
            return this;
        }
        return _p.r(11).createClass("Container", {
            getItems: function() {
                return this.items || (this.items = []);
            },
            getItem: function(index) {
                return this.getItems()[index];
            },
            getFirstItem: function() {
                return this.getItem(0);
            },
            getLastItem: function() {
                return this.getItem(this.getItems().length - 1);
            },
            indexOf: function(item) {
                return this.getItems().indexOf(item);
            },
            eachItem: function(fn) {
                var items = this.getItems(), length = items.length, i;
                for (i = 0; i < length; i++) {
                    fn.call(this, i, items[i]);
                }
                return this;
            },
            addItem: function(item, pos, noEvent) {
                var items = this.getItems(), length = items.length;
                if (~items.indexOf(item)) {
                    return this;
                }
                if (!(pos >= 0 && pos < length)) {
                    pos = length;
                }
                items.splice(pos, 0, item);
                if (typeof item === "object") {
                    item.container = this;
                    item.remove = itemRemove;
                }
                this.handleAdd(item, pos);
                if (!noEvent) {
                    this.onContainerChanged("add", [ item ]);
                }
                return this;
            },
            addItems: function(items) {
                for (var i = 0, l = items.length; i < l; i++) {
                    this.addItem(items[i], -1, true);
                }
                this.onContainerChanged("add", items);
                return this;
            },
            setItems: function(items) {
                return this.clear().addItems(items);
            },
            appendItem: function(item) {
                return this.addItem(item);
            },
            prependItem: function(item) {
                return this.addItem(item, 0);
            },
            removeItem: function(pos, noEvent) {
                if (typeof pos !== "number") {
                    return this.removeItem(this.indexOf(pos));
                }
                var items = this.getItems(), length = items.length, item = items[pos];
                if (item === undefined) {
                    return this;
                }
                items.splice(pos, 1);
                if (item.container) {
                    delete item.container;
                }
                if (item.remove) {
                    delete item.remove;
                }
                this.handleRemove(item, pos);
                if (!noEvent) {
                    this.onContainerChanged("remove", [ item ]);
                }
                return this;
            },
            clear: function() {
                var removed = [];
                var item;
                while (item = this.getFirstItem()) {
                    removed.push(item);
                    this.removeItem(0, true);
                }
                this.onContainerChanged("remove", removed);
                return this;
            },
            onContainerChanged: function(type, items) {},
            handleAdd: function(item, index) {},
            handleRemove: function(item, index) {}
        });
    }
};

//src/graphic/curve.js
/*
 * 曲线
 * */
_p[30] = {
    value: function(require, exports, module) {
        var Utils = _p.r(12), CurveUtil = {
            /*
             * 获取由两个以上的点组成的曲线的平移线
             * @param points 曲线上的点的集合， 集合中的点的数量必须大于2
             * @return 平移线数组
             */
            getCurvePanLines: function(points, smoothFactor) {
                //计算原始点的中点坐标
                var centerPoints = CurveUtil.getCenterPoints(points), //注意：计算中点连线的中点坐标， 得出平移线
                panLines = CurveUtil.getPanLine(points.length, centerPoints);
                //平移线移动到顶点
                return CurveUtil.getMovedPanLines(points, panLines, smoothFactor);
            },
            /*
             * 计算给定点集合的连线的中点
             * @param points
             */
            getCenterPoints: function(points) {
                var centerPoints = {}, key = null;
                for (var i = 0, j = 0, len = points.length; i < len; i++) {
                    //j是下一个点的索引
                    j = i === len - 1 ? 0 : i + 1;
                    key = i + "," + j;
                    //计算中点坐标
                    centerPoints[key] = {
                        x: (points[i].x + points[j].y) / 2,
                        y: (points[i].x + points[j].y) / 2
                    };
                }
                return centerPoints;
            },
            /*
             * 对getCenterPoints()接口获取到的数据做处理， 计算出各个顶点对应的平移线数据
             * @param length 集合中点的个数
             * @param points 点集合， 该集合应该是getCenterPoints()接口返回的数据
             */
            getPanLine: function(length, points) {
                var result = {}, //顶点索引
                pointIndex = null;
                for (var i = 0, j; i < length; i++) {
                    var point1 = null, point2 = null;
                    //计算当前点
                    j = (i + 1) % length;
                    //保存当前处理的顶点索引
                    pointIndex = j;
                    point1 = points[i + "," + j];
                    //计算下一个点
                    i = j;
                    j = (i + 1) % length;
                    point2 = points[i + "," + j];
                    result[pointIndex] = {
                        points: [ {
                            x: point1.x,
                            y: point1.y
                        }, {
                            x: point2.x,
                            y: point2.y
                        } ],
                        center: {
                            x: (point1.x + point2.x) / 2,
                            y: (point1.y + point2.y) / 2
                        }
                    };
                    //还原i值
                    i = (pointIndex + length - 1) % length;
                }
                return result;
            },
            /*
             * 计算平移线移动到顶点后的位置
             * @param points 顶点集合
             * @param panLines 平移线集合
             */
            getMovedPanLines: function(points, panLines, smoothFactor) {
                var result = {};
                Utils.each(points, function(point, index) {
                    //当前平移线
                    var currentPanLine = panLines[index], //平移线中点
                    center = currentPanLine.center, //移动距离
                    distance = {
                        x: center.x - point.x,
                        y: center.y - point.y
                    };
                    var currentResult = result[index] = {
                        points: [],
                        center: {
                            x: point.x,
                            y: point.y
                        }
                    };
                    //计算控制点到顶点的距离， 并且应用平滑系数到距离上
                    Utils.each(currentPanLine.points, function(controlPoint, index) {
                        var moved = {
                            x: controlPoint.x - distance.x,
                            y: controlPoint.y - distance.y
                        };
                        var vertex = currentResult.center;
                        var dx = moved.x - vertex.x;
                        var dy = moved.y - vertex.y;
                        moved.x = vertex.x + smoothFactor * dx;
                        moved.y = vertex.y + smoothFactor * dy;
                        currentResult.points.push(moved);
                    });
                });
                return result;
            }
        };
        return _p.r(11).createClass("Curve", {
            base: _p.r(46),
            mixins: [ _p.r(51) ],
            constructor: function(points, isColse) {
                this.callBase();
                this.setPoints(points || []);
                this.closeState = !!isColse;
                this.changeable = true;
                this.smoothFactor = 1;
                this.update();
            },
            //当点集合发生变化时采取的动作
            onContainerChanged: function() {
                if (this.changeable) {
                    this.update();
                }
            },
            setSmoothFactor: function(factor) {
                this.smoothFactor = factor < 0 ? 0 : factor;
                this.update();
                return this;
            },
            getSmoothFactor: function() {
                return this.smoothFactor;
            },
            update: function() {
                var points = this.getPoints(), withControlPoints = null, drawer = this.getDrawer(), curPoint = null, curControlPoint = null, prevControlPoint = null;
                drawer.clear();
                if (points.length === 0) {
                    return this;
                } else {
                    drawer.moveTo(points[0]);
                }
                if (points.length === 1) {
                    return this;
                }
                if (points.length === 2) {
                    drawer.lineTo(points[1]);
                    return this;
                }
                //获取已转换过后的带控制点的所有点
                withControlPoints = CurveUtil.getCurvePanLines(points, this.getSmoothFactor());
                for (var i = 1, len = points.length; i < len; i++) {
                    //当前顶点
                    curPoint = withControlPoints[i].center;
                    //当前控制点
                    if (this.closeState || i != len - 1) {
                        curControlPoint = withControlPoints[i].points[0];
                    } else {
                        //非闭合状态下最后一个点的处理
                        curControlPoint = withControlPoints[i].center;
                    }
                    if (this.closeState || i != 1) {
                        prevControlPoint = withControlPoints[i - 1].points[1];
                    } else {
                        //非闭合状态下第一个点的处理
                        prevControlPoint = withControlPoints[i - 1].center;
                    }
                    drawer.bezierTo(prevControlPoint.x, prevControlPoint.y, curControlPoint.x, curControlPoint.y, curPoint.x, curPoint.y);
                }
                //处理闭合
                if (this.closeState) {
                    curPoint = withControlPoints[0].center;
                    curControlPoint = withControlPoints[0].points[0];
                    prevControlPoint = withControlPoints[points.length - 1].points[1];
                    drawer.bezierTo(prevControlPoint.x, prevControlPoint.y, curControlPoint.x, curControlPoint.y, curPoint.x, curPoint.y);
                }
                return this;
            },
            close: function() {
                this.closeState = true;
                return this.update();
            },
            open: function() {
                this.closeState = false;
                return this.update();
            },
            isClose: function() {
                return !!this.closeState;
            }
        });
    }
};

//src/graphic/data.js
_p[31] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("Data", {
            constructor: function() {
                this._data = {};
            },
            setData: function(name, value) {
                this._data[name] = value;
                return this;
            },
            getData: function(name) {
                return this._data[name];
            },
            removeData: function(name) {
                delete this._data[name];
                return this;
            }
        });
    }
};

//src/graphic/ellipse.js
_p[32] = {
    value: function(require, exports, module) {
        var Utils = _p.r(12), Point = _p.r(50);
        return _p.r(11).createClass("Ellipse", {
            base: _p.r(46),
            constructor: function(rx, ry, cx, cy) {
                this.callBase();
                this.rx = rx || 0;
                this.ry = ry || 0;
                this.cx = cx || 0;
                this.cy = cy || 0;
                this.update();
            },
            update: function() {
                var rx = this.rx, ry = this.ry, x1 = this.cx + rx, x2 = this.cx - rx, y = this.cy;
                var drawer = this.getDrawer();
                drawer.clear();
                drawer.moveTo(x1, y);
                drawer.arcTo(rx, ry, 0, 1, 1, x2, y);
                drawer.arcTo(rx, ry, 0, 1, 1, x1, y);
                return this;
            },
            getRadius: function() {
                return {
                    x: this.rx,
                    y: this.ry
                };
            },
            getRadiusX: function() {
                return this.rx;
            },
            getRadiusY: function() {
                return this.ry;
            },
            getCenter: function() {
                return new Point(this.cx, this.cy);
            },
            getCenterX: function() {
                return this.cx;
            },
            getCenterY: function() {
                return this.cy;
            },
            setRadius: function(rx, ry) {
                this.rx = rx;
                this.ry = ry;
                return this.update();
            },
            setRadiusX: function(rx) {
                this.rx = rx;
                return this.update();
            },
            setRadiusY: function(ry) {
                this.ry = ry;
                return this.update();
            },
            setCenter: function(cx, cy) {
                if (arguments.length == 1) {
                    var p = Point.parse(arguments[0]);
                    cx = p.x;
                    cy = p.y;
                }
                this.cx = cx;
                this.cy = cy;
                return this.update();
            },
            setCenterX: function(cx) {
                this.cx = cx;
                return this.update();
            },
            setCenterY: function(cy) {
                this.cy = cy;
                return this.update();
            }
        });
    }
};

//src/graphic/eventhandler.js
/*
 * kity event 实现
 */
_p[33] = {
    value: function(require, exports, module) {
        // polyfill
        (function() {
            function CustomEvent(event, params) {
                params = params || {
                    bubbles: false,
                    cancelable: false,
                    detail: undefined
                };
                var evt = document.createEvent("CustomEvent");
                evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
                return evt;
            }
            CustomEvent.prototype = window.Event.prototype;
            window.CustomEvent = CustomEvent;
        })();
        var Utils = _p.r(12), ShapeEvent = _p.r(62);
        // 内部处理器缓存
        var INNER_HANDLER_CACHE = {}, // 用户处理器缓存
        USER_HANDLER_CACHE = {}, guid = 0;
        // 添加事件统一入口
        function _addEvent(type, handler, isOnce) {
            isOnce = !!isOnce;
            if (Utils.isString(type)) {
                type = type.match(/\S+/g);
            }
            Utils.each(type, function(currentType) {
                listen.call(this, this.node, currentType, handler, isOnce);
            }, this);
            return this;
        }
        // 移除事件统一入口
        function _removeEvent(type, handler) {
            var userHandlerList = null, eventId = this._EVNET_UID, isRemoveAll = handler === undefined;
            userHandlerList = USER_HANDLER_CACHE[eventId][type];
            //移除指定的监听器
            if (!isRemoveAll) {
                isRemoveAll = true;
                Utils.each(userHandlerList, function removeKityEvent(fn, index) {
                    if (fn === handler) {
                        // 不能结束， 需要查找完整个list， 避免丢失移除多次绑定同一个处理器的情况
                        delete userHandlerList[index];
                    } else {
                        isRemoveAll = false;
                    }
                });
            }
            //删除所有监听器
            if (isRemoveAll) {
                deleteDomEvent(this.node, type, INNER_HANDLER_CACHE[eventId][type]);
                delete USER_HANDLER_CACHE[eventId][type];
                delete INNER_HANDLER_CACHE[eventId][type];
            }
            return this;
        }
        // 执行绑定, 该方法context为shape或者mixin了eventhandler的对象
        function listen(node, type, handler, isOnce) {
            var eid = this._EVNET_UID, targetObject = this;
            // 初始化内部监听器
            if (!INNER_HANDLER_CACHE[eid]) {
                INNER_HANDLER_CACHE[eid] = {};
            }
            if (!INNER_HANDLER_CACHE[eid][type]) {
                // 内部监听器
                INNER_HANDLER_CACHE[eid][type] = function kityEventHandler(e) {
                    e = new ShapeEvent(e || window.event);
                    Utils.each(USER_HANDLER_CACHE[eid][type], function executeKityEvent(fn) {
                        var result;
                        if (fn) {
                            result = fn.call(targetObject, e);
                            //once 绑定， 执行完后删除
                            if (isOnce) {
                                targetObject.off(type, fn);
                            }
                        }
                        // 如果用户handler里return了false， 则该节点上的此后的同类型事件将不再执行
                        return result;
                    }, targetObject);
                };
            }
            // 初始化用户监听器列表
            if (!USER_HANDLER_CACHE[eid]) {
                USER_HANDLER_CACHE[eid] = {};
            }
            if (!USER_HANDLER_CACHE[eid][type]) {
                USER_HANDLER_CACHE[eid][type] = [ handler ];
                // 绑定对应类型的事件
                // dom对象利用dom event进行处理， 非dom对象， 由消息分发机制处理
                if (!!node && "on" + type in node) {
                    bindDomEvent(node, type, INNER_HANDLER_CACHE[eid][type]);
                }
            } else {
                USER_HANDLER_CACHE[eid][type].push(handler);
            }
        }
        // 绑定dom事件
        function bindDomEvent(node, type, handler) {
            if (node.addEventListener) {
                node.addEventListener(type, handler, false);
            } else {
                node.attachEvent("on" + type, handler);
            }
        }
        // 删除dom事件
        function deleteDomEvent(node, type, handler) {
            if (node.removeEventListener) {
                node.removeEventListener(type, handler, false);
            } else {
                node.detachEvent(type, handler);
            }
        }
        // 触发dom事件
        function triggerDomEvent(node, type, params) {
            var event = new CustomEvent(type, {
                bubbles: true,
                cancelable: true
            });
            event._kityParam = params;
            node.dispatchEvent(event);
        }
        // 发送消息
        function sendMessage(messageObj, type, msg) {
            var event = null, handler = null;
            var handlers = INNER_HANDLER_CACHE[messageObj._EVNET_UID];
            if (!handlers) return;
            handler = handlers[type];
            if (!handler) {
                return;
            }
            event = Utils.extend({
                type: type,
                target: messageObj
            }, msg || {});
            handler.call(messageObj, event);
        }
        // 对外接口
        return _p.r(11).createClass("EventHandler", {
            constructor: function() {
                this._EVNET_UID = ++guid;
            },
            addEventListener: function(type, handler) {
                return _addEvent.call(this, type, handler, false);
            },
            addOnceEventListener: function(type, handler) {
                return _addEvent.call(this, type, handler, true);
            },
            removeEventListener: function(type, handler) {
                return _removeEvent.call(this, type, handler);
            },
            on: function(type, handler) {
                return this.addEventListener.apply(this, arguments);
            },
            once: function(type, handler) {
                return this.addOnceEventListener.apply(this, arguments);
            },
            off: function() {
                return this.removeEventListener.apply(this, arguments);
            },
            fire: function(type, params) {
                return this.trigger.apply(this, arguments);
            },
            trigger: function(type, params) {
                sendMessage(this, type, params);
                return this;
            }
        });
    }
};

//src/graphic/geometry.js
_p[34] = {
    value: function(require) {
        var utils = _p.r(12);
        var Point = _p.r(50);
        var Vector = _p.r(73);
        var Matrix = _p.r(43);
        var g = {};
        var pathCommand = /([achlmrqstvz])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?\s*)+)/gi, pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)\s*,?\s*/gi, paramCounts = {
            a: 7,
            c: 6,
            h: 1,
            l: 2,
            m: 2,
            q: 4,
            s: 4,
            t: 2,
            v: 1,
            z: 0
        };
        function pathClone(path) {
            var result, i, j, segment, segmentCopy;
            result = [];
            for (i = 0; i < path.length; i++) {
                segment = path[i];
                result.push(segmentCopy = []);
                for (j = 0; j < segment.length; j++) {
                    segmentCopy.push(segment[j]);
                }
            }
            if (path.isUniform) result.isUniform = true;
            if (path.isAbsolute) result.isAbsolute = true;
            if (path.isCurve) result.isCurve = true;
            return result;
        }
        // 缓存函数
        // from raphael.js
        function cacher(f, scope, postprocessor) {
            function repush(array, item) {
                for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
                    return array.push(array.splice(i, 1)[0]);
                }
            }
            function newf() {
                var arg = Array.prototype.slice.call(arguments, 0), args = arg.join("␀"), cache = newf.cache = newf.cache || {}, count = newf.count = newf.count || [];
                if (cache.hasOwnProperty(args)) {
                    repush(count, args);
                    return postprocessor ? postprocessor(cache[args]) : cache[args];
                }
                if (count.length >= 1e3) {
                    delete cache[count.shift()];
                }
                count.push(args);
                cache[args] = f.apply(scope, arg);
                return postprocessor ? postprocessor(cache[args]) : cache[args];
            }
            return newf;
        }
        /**
     *
     * kity.g.pathToString(pathSegment)
     *
     * 返回表示 PathSegment 的字符串
     *
     * @param  {Array} pathSegment
     *     要表示的 Path Segment
     *
     * @return {String} 表示该 Path 的字符串
     *
     * @example
     *
     *     var pathSegment = [['M', 0, 0], ['L', 10, 10]]
     *     var pathString = kity.g.pathToString(pathSegment);
     *     // 返回 'M0,0L10,10'
     */
        g.pathToString = function(pathSegment) {
            pathSegment = pathSegment || this;
            if (typeof pathSegment == "string") return pathSegment;
            if (pathSegment instanceof Array) {
                pathSegment = utils.flatten(pathSegment);
                return pathSegment.join(",").replace(/,?([achlmqrstvxz]),?/gi, "$1");
            }
        };
        /**
     * kity.g.parsePathString(pathString)
     *
     * 解析 Path 字符串成 PathSegment
     *
     * @copyright rapheal.js
     *
     * @example
     *
     *     var seg = kity.g.parsePathString('M10,12l21-23-21.5,11z');
     *     // 返回: [['M', 10, 12], ['l', 21, -23], ['l', -21.5, 11], ['z']]
     *
     * @param  {String} pathString Path 字符串
     * @return {Array}
     */
        g.parsePathString = cacher(function(pathString) {
            var data = [];
            pathString.replace(pathCommand, function(a, b, c) {
                var params = [], name = b.toLowerCase();
                c.replace(pathValues, function(a, b) {
                    if (b) params.push(+b);
                });
                if (name == "m" && params.length > 2) {
                    data.push([ b ].concat(params.splice(0, 2)));
                    name = "l";
                    b = b == "m" ? "l" : "L";
                }
                if (name == "r") {
                    data.push([ b ].concat(params));
                } else {
                    while (params.length >= paramCounts[name]) {
                        data.push([ b ].concat(params.splice(0, paramCounts[name])));
                        if (!paramCounts[name]) {
                            break;
                        }
                    }
                }
            });
            data.isUniform = true;
            data.toString = g.pathToString;
            return data;
        });
        /**
     * kity.g.pathToAbsolute(path)
     *
     * 把路径转换为绝对路径的形式
     *
     * @param {Array|String} path
     *     要转换的 path 路径或者数组
     *
     * @return {Array}
     *     转换后的 Path Segment
     *
     * @example
     *
     *     var path = 'M10,10l50,50';
     *     var absPath = kity.g.pathToAbsolute(path);
     *     // 返回 [['M', 10, 10], ['L', 60, 60]]
     */
        g.pathToAbsolute = cacher(function(path) {
            var pathArray = path.isUniform ? path : g.parsePathString(g.pathToString(path));
            var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0;
            var r, pa, i, j, k, ii, jj, kk;
            if (pathArray[0][0] == "M") {
                x = +pathArray[0][1];
                y = +pathArray[0][2];
                mx = x;
                my = y;
                start++;
                res[0] = [ "M", x, y ];
            }
            for (r, pa, i = start, ii = pathArray.length; i < ii; i++) {
                res.push(r = []);
                pa = pathArray[i];
                if (pa[0] != pa[0].toUpperCase()) {
                    r[0] = pa[0].toUpperCase();
                    switch (r[0]) {
                      case "A":
                        r[1] = pa[1];
                        r[2] = pa[2];
                        r[3] = pa[3];
                        r[4] = pa[4];
                        r[5] = pa[5];
                        r[6] = +(pa[6] + x);
                        r[7] = +(pa[7] + y);
                        break;

                      case "V":
                        r[1] = +pa[1] + y;
                        break;

                      case "H":
                        r[1] = +pa[1] + x;
                        break;

                      case "M":
                        mx = +pa[1] + x;
                        my = +pa[2] + y;
                        break;

                      default:
                        for (j = 1, jj = pa.length; j < jj; j++) {
                            r[j] = +pa[j] + (j % 2 ? x : y);
                        }
                    }
                } else {
                    for (k = 0, kk = pa.length; k < kk; k++) {
                        r[k] = pa[k];
                    }
                }
                switch (r[0]) {
                  case "Z":
                    x = mx;
                    y = my;
                    break;

                  case "H":
                    x = r[1];
                    break;

                  case "V":
                    y = r[1];
                    break;

                  case "M":
                    mx = r[r.length - 2];
                    my = r[r.length - 1];
                    break;

                  default:
                    x = r[r.length - 2];
                    y = r[r.length - 1];
                }
            }
            res.isUniform = true;
            res.isAbsolute = true;
            res.toString = g.pathToString;
            return res;
        });
        // 把圆弧绘制的曲线转化为对应的三次贝塞尔形式
        function a2c(x1, y1, rx, ry, angle, laf, sf, x2, y2, recursive) {
            // copy from raphael.js
            // for more information of where this math came from visit:
            // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
            var math = Math, PI = math.PI, abs = Math.abs, _120 = PI * 120 / 180, rad = PI / 180 * (+angle || 0), res = [], xy, rotate = function(x, y, rad) {
                var X = x * math.cos(rad) - y * math.sin(rad), Y = x * math.sin(rad) + y * math.cos(rad);
                return {
                    x: X,
                    y: Y
                };
            };
            var cos, sin, h, x, y, rx2, ry2, k, cx, cy, f1, f2, df, f2old, x2old, y2old, c1, s1, c2, s2, t, hx, hy, m1, m2, m3, m4, newres, i, ii;
            if (!recursive) {
                xy = rotate(x1, y1, -rad);
                x1 = xy.x;
                y1 = xy.y;
                xy = rotate(x2, y2, -rad);
                x2 = xy.x;
                y2 = xy.y;
                cos = math.cos(PI / 180 * angle);
                sin = math.sin(PI / 180 * angle);
                x = (x1 - x2) / 2;
                y = (y1 - y2) / 2;
                h = x * x / (rx * rx) + y * y / (ry * ry);
                if (h > 1) {
                    h = math.sqrt(h);
                    rx = h * rx;
                    ry = h * ry;
                }
                rx2 = rx * rx;
                ry2 = ry * ry;
                k = (laf == sf ? -1 : 1) * math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
                cx = k * rx * y / ry + (x1 + x2) / 2;
                cy = k * -ry * x / rx + (y1 + y2) / 2;
                f1 = math.asin(((y1 - cy) / ry).toFixed(9));
                f2 = math.asin(((y2 - cy) / ry).toFixed(9));
                f1 = x1 < cx ? PI - f1 : f1;
                f2 = x2 < cx ? PI - f2 : f2;
                if (f1 < 0) f1 = PI * 2 + f1;
                if (f2 < 0) f2 = PI * 2 + f2;
                if (sf && f1 > f2) {
                    f1 = f1 - PI * 2;
                }
                if (!sf && f2 > f1) {
                    f2 = f2 - PI * 2;
                }
            } else {
                f1 = recursive[0];
                f2 = recursive[1];
                cx = recursive[2];
                cy = recursive[3];
            }
            df = f2 - f1;
            if (abs(df) > _120) {
                f2old = f2;
                x2old = x2;
                y2old = y2;
                f2 = f1 + _120 * (sf && f2 > f1 ? 1 : -1);
                x2 = cx + rx * math.cos(f2);
                y2 = cy + ry * math.sin(f2);
                res = a2c(x2, y2, rx, ry, angle, 0, sf, x2old, y2old, [ f2, f2old, cx, cy ]);
            }
            df = f2 - f1;
            c1 = math.cos(f1);
            s1 = math.sin(f1);
            c2 = math.cos(f2);
            s2 = math.sin(f2);
            t = math.tan(df / 4);
            hx = 4 / 3 * rx * t;
            hy = 4 / 3 * ry * t;
            m1 = [ x1, y1 ];
            m2 = [ x1 + hx * s1, y1 - hy * c1 ];
            m3 = [ x2 + hx * s2, y2 - hy * c2 ];
            m4 = [ x2, y2 ];
            m2[0] = 2 * m1[0] - m2[0];
            m2[1] = 2 * m1[1] - m2[1];
            if (recursive) {
                return [ m2, m3, m4 ].concat(res);
            } else {
                res = [ m2, m3, m4 ].concat(res).join().split(",");
                newres = [];
                for (i = 0, ii = res.length; i < ii; i++) {
                    newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
                }
                return newres;
            }
        }
        // 把二次贝塞尔曲线参数转化为三次贝塞尔曲线参数
        function q2c(x1, y1, ax, ay, x2, y2) {
            // copy from raphael.js
            var _13 = 1 / 3, _23 = 2 / 3;
            return [ _13 * x1 + _23 * ax, _13 * y1 + _23 * ay, _13 * x2 + _23 * ax, _13 * y2 + _23 * ay, x2, y2 ];
        }
        /**
     * kity.g.pathToCurve(path)
     *
     * 把路径转换为贝塞尔路径
     *
     * @param  {Array|String} path
     *     要转换的 path 路径或数组
     *
     * @return {Array}
     *     转换后的 PathSegment，每一段都是 'C'
     */
        g.pathToCurve = cacher(function(path) {
            var i, j, command, param;
            var initPoint, currentPoint, endPoint, shouldClose, lastControlPoint, aussumedControlPoint;
            var controlPoint1, controlPoint2;
            var res = [];
            // 处理的路径要求是一个绝对路径
            if (!path.isAbsolute) path = g.pathToAbsolute(path);
            for (i = 0; i < path.length; i++) {
                command = path[i][0];
                param = path[i].slice(1);
                // 画笔移动
                if (command == "M") {
                    initPoint = lastControlPoint = currentPoint = param;
                    res.push(path[i]);
                    continue;
                }
                // 路径闭合
                if (command == "Z") {
                    shouldClose = true;
                    command = "L";
                    param = initPoint;
                }
                // 绘制命令的目的位置
                endPoint = param.slice(param.length - 2);
                // 对 'H' 命令的修正
                if (command == "H") {
                    endPoint = [ param[0], currentPoint[1] ];
                    command = "L";
                }
                // 对 'V' 命令的修正
                if (command == "V") {
                    endPoint = [ currentPoint[0], param[0] ];
                    command = "L";
                }
                // 对 'S' 命令求出隐含的控制点位置
                if (command == "S" || command == "T") {
                    // 隐含控制点是上一个控制点关于当前位置的镜像
                    aussumedControlPoint = [ currentPoint[0] + (currentPoint[0] - lastControlPoint[0]), currentPoint[1] + (currentPoint[1] - lastControlPoint[1]) ];
                }
                // 针对不同的命令求控制点
                switch (command) {
                  case "L":
                    controlPoint1 = currentPoint;
                    controlPoint2 = endPoint;
                    break;

                  case "C":
                    controlPoint1 = param.slice(0, 2);
                    controlPoint2 = param.slice(2, 4);
                    break;

                  case "S":
                    controlPoint1 = aussumedControlPoint.slice();
                    controlPoint2 = param.slice(0, 2);
                    break;

                  case "Q":
                    lastControlPoint = param.slice(0, 2);
                    param = q2c.apply(null, currentPoint.concat(param));
                    controlPoint1 = param.slice(0, 2);
                    controlPoint2 = param.slice(2, 4);
                    break;

                  case "T":
                    param = q2c.apply(null, currentPoint.concat(aussumedControlPoint).concat(param));
                    controlPoint1 = param.slice(0, 2);
                    controlPoint2 = param.slice(2, 4);
                    break;

                  case "A":
                    param = a2c.apply(null, currentPoint.concat(param));
                    j = 0;
                    while (j in param) {
                        controlPoint1 = param.slice(j, j + 2);
                        controlPoint2 = param.slice(j + 2, j + 4);
                        endPoint = param.slice(j + 4, j + 6);
                        // 写入当前一段曲线
                        res.push([ "C" ].concat(controlPoint1).concat(controlPoint2).concat(endPoint));
                        j += 6;
                    }
                    break;
                }
                if (command != "A") {
                    // 写入当前一段曲线
                    res.push([ "C" ].concat(controlPoint1).concat(controlPoint2).concat(endPoint));
                }
                // 为下次循环准备当前位置
                currentPoint = endPoint;
                // 二次贝塞尔曲线自己已经记录了上个控制点的位置，其它的记录控制点 2 的位置
                if (command != "Q") {
                    lastControlPoint = controlPoint2;
                }
                if (shouldClose) {
                    res.push([ "Z" ]);
                    shouldClose = false;
                }
            }
            res.isUniform = true;
            res.isAbsolute = true;
            res.isCurve = true;
            res.toString = g.pathToString;
            return res;
        });
        /**
     * 将贝塞尔曲线切成两部分
     *
     * @see http://stackoverflow.com/questions/18655135/divide-bezier-curve-into-two-equal-halves
     */
        function cutBezier(bezierArray, t) {
            function __(t) {
                return function(p, q) {
                    return p + t * (q - p);
                };
            }
            var _ = __(t || .5), ba = bezierArray, ax = ba[0], ay = ba[1], bx = ba[2], by = ba[3], cx = ba[4], cy = ba[5], dx = ba[6], dy = ba[7], ex = _(ax, bx), ey = _(ay, by), fx = _(bx, cx), fy = _(by, cy), gx = _(cx, dx), gy = _(cy, dy), hx = _(ex, fx), hy = _(ey, fy), jx = _(fx, gx), jy = _(fy, gy), kx = _(hx, jx), ky = _(hy, jy);
            return [ [ ax, ay, ex, ey, hx, hy, kx, ky ], [ kx, ky, jx, jy, gx, gy, dx, dy ] ];
        }
        /**
     * kity.g.cutBezier(bezierArray, t)
     *
     * 在指定位置把贝塞尔曲线切割为两部分
     *
     * @param {Array} bezierArray
     *     表示贝塞尔曲线的一个数组 [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y]
     *     p1 和 p2 是贝塞尔曲线的起点和终点，c1 和 c2 是两个控制点
     *
     * @param {Number} t
     *     切割的位置（0 到 1）
     *
     * @return {Array}
     *     切割的两个贝塞尔曲线：[
     *         [p1x1, p1y1, c1x1, c1y1, c2x1, c2y1, p2x1, p2y1],
     *         [p1x2, p1y2, c1x2, c1y2, c2x2, c2y2, p2x2, p2y2]
     *     ]
     *
     */
        g.cutBezier = cacher(cutBezier);
        /**
     * 求一段贝塞尔曲线的子段
     *
     * @param {Array} bezierArray
     *     长度为 8 的数组，表示 [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y]
     *
     * @param {Number} t
     *     子段的结束位置（0 到 1）
     *
     * @param {Number} t0
     *     字段的开始位置（0 到 t），可不传，默认为 0
     *
     * @return {Array}
     *     长度为 8 的数组，表示给定贝塞尔曲线的子段
     */
        g.subBezier = function(bezierArray, t, t0) {
            var b2t = cutBezier(bezierArray, t)[0];
            return t0 ? cutBezier(b2t, t0 / t)[1] : b2t;
        };
        /**
     * 求贝塞尔曲线上的一个点
     *
     * @param {Array} bezierArray
     *     长度为 8 的数组，表示 [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y]
     *
     * @param {Number} t
     *     所求点的开始位置（0 到 1）
     *
     * @return {Point} p
     *     p.x: x 坐标
     *     p.y: y 坐标
     *     p.tan: 在 t 处的切线方向（类型为 kity.Vector，模为 1）
     */
        g.pointAtBezier = function(bezierArray, t) {
            var b2t = cutBezier(bezierArray, t)[0];
            var p = Point.parse(b2t.slice(6)), c = Point.parse(b2t.slice(4, 2)), v = Vector.fromPoints(c, p);
            if (t === 0) {
                p.tan = g.pointAtBezier(bezierArray, .01).tan;
            } else {
                p.tan = v.normalize();
            }
            return p;
        };
        /**
     * 求贝塞尔曲线的长度
     *
     * @param {Array} bezierArray
     *     长度为 8 的数组，表示 [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y]
     *
     * @param {Number} tolerate
     *     允许的误差，默认是 0.1
     *
     * @return {Number} 贝塞尔曲线的长度
     */
        g.bezierLength = cacher(function bezierLength(bezierArray) {
            // 表示（c[0]*t^4 + c[1]*t^3 + c[2]*t^2 + c[3]*t^1 + c[4])^(1/2)的函数
            function f(x) {
                var m = c0 * Math.pow(x, 4) + c1 * Math.pow(x, 3) + c2 * Math.pow(x, 2) + c3 * x + c4;
                if (m < 0) {
                    m = 0;
                }
                return Math.pow(m, .5);
            }
            // 用Newton-Cotes型求积公式
            var arr = bezierArray;
            // 三次贝塞尔曲线函数求导后，求出对应的方程系数，用cx[],cy[]表示x`(t)和y`(t)的系数
            var cx0, cx1, cx2;
            var cy0, cy1, cy2;
            // 用c[]表示x`(t)^2 + y`(t)^2的结果的系数
            var c0, c1, c2, c3, c4;
            // 求x`(t) 和 y`(t)的系数
            cx0 = -3 * arr[0] + 9 * arr[2] - 9 * arr[4] + 3 * arr[6];
            cx1 = 6 * arr[0] - 12 * arr[2] + 6 * arr[4];
            cx2 = -3 * arr[0] + 3 * arr[2];
            cy0 = -3 * arr[1] + 9 * arr[3] - 9 * arr[5] + 3 * arr[7];
            cy1 = 6 * arr[1] - 12 * arr[3] + 6 * arr[5];
            cy2 = -3 * arr[1] + 3 * arr[3];
            // 求x`(t)^2 + y`(t)^2的结果的系数 c[]
            c0 = Math.pow(cx0, 2) + Math.pow(cy0, 2);
            c1 = 2 * (cx0 * cx1 + cy0 * cy1);
            c2 = 2 * (cx0 * cx2 + cy0 * cy2) + Math.pow(cx1, 2) + Math.pow(cy1, 2);
            c3 = 2 * (cx1 * cx2 + cy1 * cy2);
            c4 = Math.pow(cx2, 2) + Math.pow(cy2, 2);
            // 用cotes积分公式求值
            return (f(0) + f(1) + 4 * (f(.125) + f(.375) + f(.625) + f(.875)) + 2 * (f(.25) + f(.5) + f(.75))) / 24;
        });
        // 计算一个 pathSegment 中每一段的在整体中所占的长度范围，以及总长度
        // 方法要求每一段都是贝塞尔曲线
        var getBezierPathSegmentRanges = cacher(function(pathSegment) {
            var i, ii, segment, position, bezierLength, segmentRanges, totalLength;
            segmentRanges = [];
            // 总长度
            totalLength = 0;
            for (i = 0, ii = pathSegment.length; i < ii; i++) {
                segment = pathSegment[i];
                if (segment[0] == "M") {
                    position = segment.slice(1);
                    segmentRanges.push(null);
                    continue;
                }
                if (segment[0] == "Z") {
                    segmentRanges.push(null);
                    continue;
                }
                bezierLength = g.bezierLength(position.concat(segment.slice(1)));
                segmentRanges.push([ totalLength, totalLength + bezierLength ]);
                totalLength += bezierLength;
                // 迭代当前位置
                position = segment.slice(4);
            }
            segmentRanges.totalLength = totalLength;
            return segmentRanges;
        });
        /**
     * 求一段路径的子路径
     *
     * @param  {Array|String} path
     *     原路径
     *
     * @param  {Number} t1
     *     要求的子路径的结束位置（0 到 1）
     *
     * @param  {Number} t0
     *     要求的子路径的开始位置（0 到 t1），可不传，默认为 0
     *
     * @return {Array}
     *     子路径的 PathSegment
     */
        g.subPath = function(path, t1, t0) {
            var dt;
            t0 = t0 || 0;
            dt = t1 - t0;
            dt = dt - (dt | 0);
            t0 = t0 - (t0 | 0);
            t1 = t0 + dt;
            if (t1 > 1) {
                return g.subPath(path, 1, t0).concat(g.subPath(path, t1 - 1));
            }
            if (!path.isCurve) {
                path = g.pathToCurve(path);
            }
            // path 每一段在整体中的长度区间
            var segmentRanges = getBezierPathSegmentRanges(path);
            // path 总长度
            var totalLength = segmentRanges.totalLength;
            // t1 和 t0 位置命中的长度位置
            var t1Length = totalLength * t1, t0Length = totalLength * (t0 || 0);
            // 产生的子路径
            var subPath = [];
            // 迭代变量，a 是一段的长度区间左值，b 是右值，d 是区间长度
            var i, ii, a, b, d;
            var position;
            var bezier, subBezier, stared;
            for (i = 0, ii = path.length; i < ii; i++) {
                if (path[i][0] == "M") {
                    position = path[i].slice(1);
                    if (stared) {
                        subPath.push(path[i].slice());
                    }
                    continue;
                }
                if (path[i][0] == "Z") {
                    // subpath 路径不闭合
                    continue;
                }
                a = segmentRanges[i][0];
                b = segmentRanges[i][1];
                d = b - a;
                bezier = position.concat(path[i].slice(1));
                if (t0Length > b) {
                    // t0 和 t1 都右溢出
                    // -----------------------------------
                    //            t0   t1
                    // |________|
                    //
                    // 需要跳过当前块
                    position = bezier.slice(bezier.length - 2);
                    continue;
                } else if (t0Length >= a) {
                    // 命中 t0；t1 可能命中或右溢出
                    // -----------------------------------
                    //            t0   t1
                    //     |______|__|
                    //
                    //     or:  |_|____|__|
                    //
                    // 取当前块 t0 到 t1 的部分
                    subBezier = g.subBezier(bezier, Math.min((t1Length - a) / d, 1), (t0Length - a) / d);
                    stared = true;
                    position = subBezier.slice(0, 2);
                    subPath.push([ "M" ].concat(subBezier.slice(0, 2)));
                    subPath.push([ "C" ].concat(subBezier.slice(2)));
                } else if (t1Length >= b) {
                    // t0 左溢出；t1 右溢出，整个块是需要的
                    // -----------------------------------
                    //       t0             t1
                    //          |_________|
                    //
                    // 此时取整个块
                    subPath.push(path[i].slice());
                } else if (t1Length >= a) {
                    // t0 左溢出；t1 命中，取当前块 t1 之前的部分
                    // -----------------------------------
                    //            t0   t1
                    //              |__|______|
                    // 取当前块 t1 之前的部分
                    subBezier = g.subBezier(bezier, (t1Length - a) / d);
                    subPath.push([ "C" ].concat(subBezier.slice(2)));
                    stared = false;
                } else {
                    // 没有可以再要的了
                    break;
                }
                position = bezier.slice(bezier.length - 2);
            }
            subPath.isAbsolute = true;
            subPath.isCurve = true;
            subPath.isUniform = true;
            subPath.toString = g.pathToString;
            return subPath;
        };
        /**
     * 求路径上的一个点
     *
     * @param  {Array|String} path
     *     要求点的路径
     *
     * @param  {Number} t
     *     要求的点的位置（0 到 1）
     *
     * @return {Point} p
     *     p.x: x 坐标
     *     p.y: y 坐标
     *     p.tan: 在 t 处的切线方向（类型为 kity.Vector，模为 1）
     */
        g.pointAtPath = function(path, t) {
            if (!path.isCurve) {
                path = g.pathToCurve(path);
            }
            var subPath = g.subPath(path, t);
            var lastCurve = subPath[subPath.length - 1][0] == "Z" ? subPath[subPath.length - 2] : subPath[subPath.length - 1];
            // 跳过 'C' 命令，只留参数
            lastCurve = lastCurve.slice(1);
            var p = Point.parse(lastCurve.slice(4)), c = Point.parse(lastCurve.slice(2, 4));
            p.tan = Vector.fromPoints(c, p).normalize();
            return p;
        };
        /**
     * 求一段路径的长度
     *
     * @param  {string|Array} path
     *     要求的路径
     *
     * @return {Number}
     *     路径的长度
     */
        g.pathLength = cacher(function(path) {
            if (!path.isCurve) {
                path = g.pathToCurve(path);
            }
            // path 每一段在整体中的长度区间
            var segmentRanges = getBezierPathSegmentRanges(path);
            return segmentRanges.totalLength;
        });
        /**
     * 求一段路径的关键点
     *
     * @param  {string|Array} path
     *     要求的路径
     *
     * @return {Array}
     *     关键点的集合
     */
        g.pathKeyPoints = cacher(function(path) {
            var i, ii, command, keyPoints;
            if (!path.isCurve) {
                path = g.pathToCurve(path);
            }
            keyPoints = [];
            for (i = 0, ii = path.length; i < ii; i++) {
                if (path[i][0] == "z") continue;
                keyPoints.push(path[i].slice(path[i].length - 2));
            }
            return keyPoints;
        });
        // 对比两个路径的关键位置，在合适的位置切割合适的路径，使得两个路径的段数一致
        // TODO: 使用插值算法，使对应点更合理
        var alignCurve = cacher(function(path1, path2) {
            if (!path1.isCurve) path1 = g.pathToCurve(path1);
            if (!path2.isCurve) path2 = g.pathToCurve(path2);
            var p1 = pathClone(path1);
            var p2 = pathClone(path2);
            p1.i = 0;
            p2.i = 0;
            p1.o = p2;
            p2.o = p1;
            function command(p, i) {
                return p[i || p.i] && p[i || p.i][0];
            }
            function param(p, i) {
                return p[i || p.i] && p[i || p.i].slice(1);
            }
            function point(p, i) {
                var _param = param(p, i);
                return _param && _param.slice(-2);
            }
            function fixZ(p) {
                if (command(p) == "Z") {
                    p.splice(p.i, 1);
                    return true;
                }
                return false;
            }
            function fixM(p) {
                if (command(p) == "M") {
                    p.o.splice(p.o.i, 0, [ "M" ].concat(point(p.o, p.o.i - 1)));
                    p.i++;
                    p.o.i++;
                    return true;
                }
                return false;
            }
            function fill(p) {
                var lastPoint;
                var i = 1;
                while (!lastPoint) {
                    lastPoint = point(p, p.length - i++);
                }
                p.o.i = p.i;
                while (p.length < p.o.length) {
                    if (fixZ(p.o)) continue;
                    if (fixM(p.o)) continue;
                    p.push([ "C" ].concat(lastPoint).concat(lastPoint).concat(lastPoint));
                    p.i++;
                    p.o.i++;
                }
            }
            while (p1.i < p1.length && p2.i < p2.length) {
                if (fixZ(p1) || fixZ(p2)) continue;
                if (command(p1) == command(p2)) {
                    p1.i++;
                    p2.i++;
                    continue;
                }
                if (fixM(p1) || fixM(p2)) continue;
                p1.i++;
                p2.i++;
            }
            if (p1.i == p1.length) fill(p1);
            if (p2.i == p2.length) fill(p2);
            delete p1.i;
            delete p1.o;
            delete p2.i;
            delete p2.o;
            return [ p1, p2 ];
        });
        g.alignCurve = alignCurve;
        /**
     * 获得两个路径的补间结果
     *
     * @param  {string|Array} path1
     *     补间起始路径
     *
     * @param  {string|Array} path2
     *     补间结束路径
     *
     * @param  {Number} t
     *     补间比例，0 返回跟 path1 等效的结果；1 返回跟 path2 等效的结果
     *
     * @return {PathSegment}
     *     补间的结果
     */
        g.pathTween = function(path1, path2, t) {
            if (t === 0) return path1;
            if (t === 1) return path2;
            var aligned = alignCurve(path1, path2);
            var result = [], seg, i, j;
            path1 = aligned[0];
            path2 = aligned[1];
            for (i = 0; i < path1.length; i++) {
                result.push(seg = []);
                seg.push(path1[i][0]);
                for (j = 1; j < path1[i].length; j++) {
                    seg.push(path1[i][j] + t * (path2[i][j] - path1[i][j]));
                }
            }
            result.isUniform = result.isCurve = result.isAbsolute = true;
            return result;
        };
        /**
     * 变换指定的路径
     *
     * @param  {String|Array} path
     *     需要变换的路径
     *
     * @param  {kity.Matrix} matrix
     *     使用的变换矩阵
     *
     * @return {Array}
     *     变换后的路径
     */
        g.transformPath = cacher(function(path, matrix) {
            var i, ii, j, result, seg, pair;
            if (!path.isCurve) {
                path = g.pathToCurve(path);
            }
            result = [];
            for (i = 0, ii = path.length; i < ii; i++) {
                result.push(seg = [ path[i][0] ]);
                for (j = 1; j < path[i].length; j += 2) {
                    pair = path[i].slice(j, j + 2);
                    pair = matrix.transformPoint(Point.parse(pair));
                    result.push(pair);
                }
            }
            return result;
        });
        // entend
        _p.r(11).extendClass(Matrix, {
            transformPath: function(path) {
                return g.transformPath(path, this);
            }
        });
        return g;
    }
};

//src/graphic/gradient.js
_p[35] = {
    value: function(require, exports, module) {
        var svg = _p.r(67);
        var Resource = _p.r(58);
        var Color = _p.r(28);
        return _p.r(11).createClass("GradientBrush", {
            base: Resource,
            constructor: function(gradientNodeType, paper) {
                this.callBase(gradientNodeType, paper);
                this.stops = [];
            },
            addStop: function(offset, color, opacity) {
                var gstop = svg.createNode("stop");
                if (!(color instanceof Color)) {
                    color = Color.parse(color);
                }
                if (opacity === undefined) {
                    opacity = color.get("a");
                }
                gstop.setAttribute("offset", offset);
                gstop.setAttribute("stop-color", color.toRGB());
                if (opacity < 1) {
                    gstop.setAttribute("stop-opacity", opacity);
                }
                this.node.appendChild(gstop);
                return this;
            }
        });
    }
};

//src/graphic/group.js
_p[36] = {
    value: function(require, exports, module) {
        var ShapeContainer = _p.r(61);
        return _p.r(11).createClass("Group", {
            mixins: [ ShapeContainer ],
            base: _p.r(60),
            constructor: function Group() {
                this.callBase("g");
            }
        });
    }
};

//src/graphic/hyperlink.js
_p[37] = {
    value: function(require, exports, module) {
        var ShapeContainer = _p.r(61);
        return _p.r(11).createClass("HyperLink", {
            mixins: [ ShapeContainer ],
            base: _p.r(60),
            constructor: function(url) {
                this.callBase("a");
                this.setHref(url);
            },
            setHref: function(href) {
                this.node.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", href);
                return this;
            },
            getHref: function() {
                return this.node.getAttributeNS("xlink:href");
            },
            setTarget: function(target) {
                this.node.setAttribute("target", target);
                return this;
            },
            getTarget: function() {
                return this.node.getAttribute("target");
            }
        });
    }
};

//src/graphic/image.js
_p[38] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("Image", {
            base: _p.r(60),
            constructor: function(url, width, height, x, y) {
                this.callBase("image");
                this.url = url;
                this.width = width || 0;
                this.height = height || 0;
                this.x = x || 0;
                this.y = y || 0;
                this.update();
            },
            update: function() {
                this.node.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", this.url);
                this.node.setAttribute("x", this.x);
                this.node.setAttribute("y", this.y);
                this.node.setAttribute("width", this.width);
                this.node.setAttribute("height", this.height);
                return this;
            },
            setUrl: function(url) {
                this.url = url === "" ? null : url;
                return this.update();
            },
            getUrl: function() {
                return this.url;
            },
            setWidth: function(width) {
                this.width = width;
                return this.update();
            },
            getWidth: function() {
                return this.width;
            },
            setHeight: function(height) {
                this.height = height;
                return this.update();
            },
            getHeight: function() {
                return this.height;
            },
            setX: function(x) {
                this.x = x;
                return this.update();
            },
            getX: function() {
                return this.x;
            },
            setY: function(y) {
                this.y = y;
                return this.update();
            },
            getY: function() {
                return this.y;
            }
        });
    }
};

//src/graphic/line.js
_p[39] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("Line", {
            base: _p.r(46),
            constructor: function(x1, y1, x2, y2) {
                this.callBase();
                this.point1 = {
                    x: x1 || 0,
                    y: y1 || 0
                };
                this.point2 = {
                    x: x2 || 0,
                    y: y2 || 0
                };
                this.update();
            },
            setPoint1: function(x, y) {
                this.point1.x = x;
                this.point1.y = y;
                return this.update();
            },
            setPoint2: function(x, y) {
                this.point2.x = x;
                this.point2.y = y;
                return this.update();
            },
            getPoint1: function() {
                return {
                    x: this.point1.x,
                    y: this.point1.y
                };
            },
            getPoint2: function() {
                return {
                    x: this.point2.x,
                    y: this.point2.y
                };
            },
            update: function() {
                var drawer = this.getDrawer();
                drawer.clear();
                drawer.moveTo(this.point1.x, this.point1.y);
                drawer.lineTo(this.point2.x, this.point2.y);
                return this;
            }
        });
    }
};

//src/graphic/lineargradient.js
_p[40] = {
    value: function(require, exports, module) {
        var svg = _p.r(67);
        var Gradient = _p.r(35);
        return _p.r(11).createClass("LinearGradientBrush", {
            base: Gradient,
            constructor: function(paper) {
                this.callBase("linearGradient", paper);
                this.setStartPosition(0, 0);
                this.setEndPosition(1, 0);
            },
            setStartPosition: function(px, py) {
                this.node.setAttribute("x1", px);
                this.node.setAttribute("y1", py);
                return this;
            },
            setEndPosition: function(px, py) {
                this.node.setAttribute("x2", px);
                this.node.setAttribute("y2", py);
                return this;
            },
            getStartPosition: function() {
                return {
                    x: +this.node.getAttribute("x1"),
                    y: +this.node.getAttribute("y1")
                };
            },
            getEndPosition: function() {
                return {
                    x: +this.node.getAttribute("x2"),
                    y: +this.node.getAttribute("y2")
                };
            }
        });
    }
};

//src/graphic/marker.js
_p[41] = {
    value: function(require, exports, module) {
        var Point = _p.r(50);
        var Marker = _p.r(11).createClass("Marker", {
            base: _p.r(58),
            mixins: [ _p.r(61), _p.r(75) ],
            constructor: function() {
                this.callBase("marker");
                this.setOrient("auto");
            },
            setRef: function(x, y) {
                if (arguments.length === 1) {
                    y = x.y;
                    x = x.x;
                }
                this.node.setAttribute("refX", x);
                this.node.setAttribute("refY", y);
                return this;
            },
            getRef: function() {
                return new Point(+this.node.getAttribute("refX"), +this.node.getAttribute("refY"));
            },
            setWidth: function(width) {
                this.node.setAttribute("markerWidth", this.width = width);
                return this;
            },
            setOrient: function(orient) {
                this.node.setAttribute("orient", this.orient = orient);
                return this;
            },
            getOrient: function() {
                return this.orient;
            },
            getWidth: function() {
                return +this.width;
            },
            setHeight: function(height) {
                this.node.setAttribute("markerHeight", this.height = height);
                return this;
            },
            getHeight: function() {
                return +this.height;
            }
        });
        var Path = _p.r(46);
        _p.r(11).extendClass(Path, {
            setMarker: function(marker, pos) {
                pos = pos || "end";
                if (!marker) {
                    this.node.removeAttribute("marker-" + pos);
                } else {
                    this.node.setAttribute("marker-" + pos, marker.toString());
                }
                return this;
            }
        });
        return Marker;
    }
};

//src/graphic/mask.js
/**
 * 蒙板
 */
_p[42] = {
    value: function(require, exports, module) {
        var Class = _p.r(11);
        var Shape = _p.r(60);
        var Mask = Class.createClass("Mask", {
            base: Shape,
            mixins: [ _p.r(61) ],
            constructor: function() {
                this.callBase("mask");
            },
            mask: function(shape) {
                shape.getNode().setAttribute("mask", "url(#" + this.getId() + ")");
                return this;
            }
        });
        Class.extendClass(Shape, {
            maskWith: function(mask) {
                mask.mask(this);
                return this;
            }
        });
        return Mask;
    }
};

//src/graphic/matrix.js
_p[43] = {
    value: function(require, exports, module) {
        var utils = _p.r(12);
        var Box = _p.r(25);
        var mPattern = /matrix\s*\((.+)\)/i;
        var Point = _p.r(50);
        // 注意，合并的结果是先执行m2，再执行m1的结果
        function mergeMatrixData(m2, m1) {
            return {
                a: m1.a * m2.a + m1.c * m2.b,
                b: m1.b * m2.a + m1.d * m2.b,
                c: m1.a * m2.c + m1.c * m2.d,
                d: m1.b * m2.c + m1.d * m2.d,
                e: m1.a * m2.e + m1.c * m2.f + m1.e,
                f: m1.b * m2.e + m1.d * m2.f + m1.f
            };
        }
        function d2r(deg) {
            return deg * Math.PI / 180;
        }
        var Matrix = _p.r(11).createClass("Matrix", {
            constructor: function() {
                if (arguments.length) {
                    this.setMatrix.apply(this, arguments);
                } else {
                    this.setMatrix(1, 0, 0, 1, 0, 0);
                }
            },
            translate: function(x, y) {
                this.m = mergeMatrixData(this.m, {
                    a: 1,
                    c: 0,
                    e: x,
                    b: 0,
                    d: 1,
                    f: y
                });
                return this;
            },
            rotate: function(deg) {
                var rad = d2r(deg);
                var sin = Math.sin(rad), cos = Math.cos(rad);
                this.m = mergeMatrixData(this.m, {
                    a: cos,
                    c: -sin,
                    e: 0,
                    b: sin,
                    d: cos,
                    f: 0
                });
                return this;
            },
            scale: function(sx, sy) {
                if (sy === undefined) {
                    sy = sx;
                }
                this.m = mergeMatrixData(this.m, {
                    a: sx,
                    c: 0,
                    e: 0,
                    b: 0,
                    d: sy,
                    f: 0
                });
                return this;
            },
            skew: function(degX, degY) {
                if (degY === undefined) {
                    degY = degX;
                }
                var tx = Math.tan(d2r(degX)), ty = Math.tan(d2r(degY));
                this.m = mergeMatrixData(this.m, {
                    a: 1,
                    c: tx,
                    e: 0,
                    b: ty,
                    d: 1,
                    f: 0
                });
                return this;
            },
            /**
         * 获得反转矩阵
         *
         * 这是我解方程算出来的
         */
            inverse: function() {
                var m = this.m, a = m.a, b = m.b, c = m.c, d = m.d, e = m.e, f = m.f, k, aa, bb, cc, dd, ee, ff;
                k = a * d - b * c;
                aa = d / k;
                bb = -b / k;
                cc = -c / k;
                dd = a / k;
                ee = (c * f - e * d) / k;
                ff = (b * e - a * f) / k;
                return new Matrix(aa, bb, cc, dd, ee, ff);
            },
            setMatrix: function(a, b, c, d, e, f) {
                if (arguments.length === 1) {
                    this.m = utils.clone(arguments[0]);
                } else {
                    this.m = {
                        a: a,
                        b: b,
                        c: c,
                        d: d,
                        e: e,
                        f: f
                    };
                }
                return this;
            },
            getMatrix: function() {
                return utils.clone(this.m);
            },
            getTranslate: function() {
                var m = this.m;
                return {
                    x: m.e / m.a,
                    y: m.f / m.d
                };
            },
            mergeMatrix: function(matrix) {
                return new Matrix(mergeMatrixData(this.m, matrix.m));
            },
            merge: function(matrix) {
                return this.mergeMatrix(matrix);
            },
            toString: function() {
                return this.valueOf().join(" ");
            },
            valueOf: function() {
                var m = this.m;
                return [ m.a, m.b, m.c, m.d, m.e, m.f ];
            },
            equals: function(matrix) {
                var m1 = this.m, m2 = matrix.m;
                return m1.a == m2.a && m1.b == m2.b && m1.c == m2.c && m1.d == m2.d && m1.e == m2.e && m1.f == m2.f;
            },
            transformPoint: function() {
                return Matrix.transformPoint.apply(null, [].slice.call(arguments).concat([ this.m ]));
            },
            transformBox: function(box) {
                return Matrix.transformBox(box, this.m);
            },
            clone: function() {
                return new Matrix(this.m);
            }
        });
        Matrix.parse = function(str) {
            var match;
            var f = parseFloat;
            if (str instanceof Array) {
                return new Matrix({
                    a: str[0],
                    b: str[1],
                    c: str[2],
                    d: str[3],
                    e: str[4],
                    f: str[5]
                });
            }
            if (match = mPattern.exec(str)) {
                var values = match[1].split(",");
                if (values.length != 6) {
                    values = match[1].split(" ");
                }
                return new Matrix({
                    a: f(values[0]),
                    b: f(values[1]),
                    c: f(values[2]),
                    d: f(values[3]),
                    e: f(values[4]),
                    f: f(values[5])
                });
            }
            return new Matrix();
        };
        Matrix.transformPoint = function(x, y, m) {
            if (arguments.length === 2) {
                m = y;
                y = x.y;
                x = x.x;
            }
            return new Point(m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f);
        };
        Matrix.transformBox = function(box, matrix) {
            var xMin = Number.MAX_VALUE, xMax = -Number.MAX_VALUE, yMin = Number.MAX_VALUE, yMax = -Number.MAX_VALUE;
            var bps = [ [ box.x, box.y ], [ box.x + box.width, box.y ], [ box.x, box.y + box.height ], [ box.x + box.width, box.y + box.height ] ];
            var bp, rp, rps = [];
            while (bp = bps.pop()) {
                rp = Matrix.transformPoint(bp[0], bp[1], matrix);
                rps.push(rp);
                xMin = Math.min(xMin, rp.x);
                xMax = Math.max(xMax, rp.x);
                yMin = Math.min(yMin, rp.y);
                yMax = Math.max(yMax, rp.y);
            }
            box = new Box({
                x: xMin,
                y: yMin,
                width: xMax - xMin,
                height: yMax - yMin
            });
            utils.extend(box, {
                closurePoints: rps
            });
            return box;
        };
        // 获得从 node 到 refer 的变换矩阵
        Matrix.getCTM = function(target, refer) {
            var ctm = {
                a: 1,
                b: 0,
                c: 0,
                d: 1,
                e: 0,
                f: 0
            };
            var node = target.shapeNode || target.node;
            refer = refer || "parent";
            // 根据参照坐标系选区的不一样，返回不同的结果
            switch (refer) {
              case "screen":
                // 以浏览器屏幕为参照坐标系
                ctm = node.getScreenCTM();
                break;

              case "doc":
              case "paper":
                // 以文档（Paper）为参照坐标系
                ctm = node.getCTM();
                break;

              case "view":
              case "top":
                // 以顶层绘图容器（视野）为参照坐标系
                if (target.getPaper()) {
                    ctm = node.getTransformToElement(target.getPaper().shapeNode);
                }
                break;

              case "parent":
                // 以父容器为参照坐标系
                if (target.node.parentNode) {
                    ctm = node.getTransformToElement(target.node.parentNode);
                }
                break;

              default:
                // 其他情况，指定参照物
                if (refer.node) {
                    ctm = node.getTransformToElement(refer.shapeNode || refer.node);
                }
            }
            return ctm ? new Matrix(ctm.a, ctm.b, ctm.c, ctm.d, ctm.e, ctm.f) : new Matrix();
        };
        return Matrix;
    }
};

//src/graphic/palette.js
/**
 * 调色板
 */
_p[44] = {
    value: function(require, exports, module) {
        //标准color
        var StandardColor = _p.r(64), Color = _p.r(28), Utils = _p.r(12);
        var Palette = _p.r(11).createClass("Palette", {
            constructor: function() {
                this.color = {};
            },
            /*
         * 获取颜色名称所对应的颜色值的Color对象
         * @param name 需要获取的颜色名称
         * @return 对应颜色名称的color对象， 如果未找到对应的名称， 则返回null
         */
            get: function(name) {
                var colorValue = this.color[name] || StandardColor.EXTEND_STANDARD[name] || StandardColor.COLOR_STANDARD[name] || "";
                if (colorValue) {
                    return new Color(colorValue);
                }
                return null;
            },
            /*
         * 获取给定名称的颜色的hex值表示
         * @param name 需要获取的颜色名称
         * @return 如果找到对应的名称， 则返回该名称所对应的hex格式的值， 否则， 返回一个空字符串
         */
            getColorValue: function(name) {
                return this.color[name] || StandardColor.EXTEND_STANDARD[name] || StandardColor.COLOR_STANDARD[name] || "";
            },
            /*
         * 向调色板实例添加自己独有的颜色名称，对已存在的颜色名称， 将会覆盖掉
         * @param name 新添加的颜色名称
         * @param value 新添加的颜色名称所对应的值， 可以是一个合法的颜色字符串或者是一个color对象
         * @return 新添加的颜色的值
         */
            add: function(name, value) {
                if (typeof value === "string") {
                    this.color[name] = new Color(value).toRGBA();
                } else {
                    this.color[name] = value.toRGBA();
                }
                return value;
            },
            /*
         * 删除调色板实例上用户自己添加的颜色， 该方法不能删除内置的颜色
         * @param name 需要删除的颜色名称
         * @return 删除是否成功的bool值
         */
            remove: function(name) {
                if (this.color.hasOwnProperty(name)) {
                    delete this.color[name];
                    return true;
                }
                return false;
            }
        });
        Utils.extend(Palette, {
            getColor: function(name) {
                var colorValue = StandardColor.EXTEND_STANDARD[name] || StandardColor.COLOR_STANDARD[name];
                if (colorValue) {
                    return new Color(colorValue);
                }
                return null;
            },
            /*
         * 通过给定的名字获取标准的颜色值表示， 返回的值以hex的方式提供
         * @param name 需要获取的标准颜色名称
         * @return 名字所对应的颜色值的hex表示， 如果未找到对应名称的值， 则返回一个空字符串
         */
            getColorValue: function(name) {
                return StandardColor.EXTEND_STANDARD[name] || StandardColor.COLOR_STANDARD[name] || "";
            },
            /*
         * 向调色板添加颜色名称，新添加的颜色对所有的调色板对象都可见
         * 对已存在的颜色名称， 将会覆盖掉
         * @param name 新添加的颜色名称
         * @param value 新添加的颜色名称所对于的值， 应该是一个hex格式的颜色字符串， 如： ”#ff0000“
         * @return 新添加的颜色的值
         */
            addColor: function(name, value) {
                if (typeof value === "string") {
                    StandardColor.EXTEND_STANDARD[name] = new Color(value).toRGBA();
                } else {
                    StandardColor.EXTEND_STANDARD[name] = value.toRGBA();
                }
                return value;
            },
            /*
         * 删除用户自己添加的颜色， 该方法不能删除内置的颜色， 该方法不会影响调色板实例自由的颜色
         * @param name 需要删除的颜色名称
         * @return 删除是否成功的bool值
         */
            removeColor: function(name) {
                if (StandardColor.EXTEND_STANDARD.hasOwnProperty(name)) {
                    delete StandardColor.EXTEND_STANDARD[name];
                    return true;
                }
                return false;
            }
        });
        return Palette;
    }
};

//src/graphic/paper.js
_p[45] = {
    value: function(require, exports, module) {
        var Class = _p.r(11);
        var utils = _p.r(12);
        var svg = _p.r(67);
        var Container = _p.r(29);
        var ShapeContainer = _p.r(61);
        var ViewBox = _p.r(75);
        var EventHandler = _p.r(33);
        var Styled = _p.r(66);
        var Matrix = _p.r(43);
        var Paper = Class.createClass("Paper", {
            mixins: [ ShapeContainer, EventHandler, Styled, ViewBox ],
            constructor: function(container) {
                this.callBase();
                this.node = this.createSVGNode();
                this.node.paper = this;
                this.node.appendChild(this.resourceNode = svg.createNode("defs"));
                this.node.appendChild(this.shapeNode = svg.createNode("g"));
                this.resources = new Container();
                this.setWidth("100%").setHeight("100%");
                if (container) {
                    this.renderTo(container);
                }
                this.callMixin();
            },
            renderTo: function(container) {
                if (utils.isString(container)) {
                    container = document.getElementById(container);
                }
                this.container = container;
                container.appendChild(this.node);
            },
            createSVGNode: function() {
                var node = svg.createNode("svg");
                node.setAttribute("xmlns", "http://www.w3.org/2000/svg");
                node.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
                node.setAttribute("version", "1.1");
                return node;
            },
            getNode: function() {
                return this.node;
            },
            getContainer: function() {
                return this.container;
            },
            getWidth: function() {
                return this.node.clientWidth;
            },
            setWidth: function(width) {
                this.node.setAttribute("width", width);
                return this;
            },
            getHeight: function() {
                return this.node.clientHeight;
            },
            setHeight: function(height) {
                this.node.setAttribute("height", height);
                return this;
            },
            setViewPort: function(cx, cy, zoom) {
                var viewport, box;
                if (arguments.length == 1) {
                    viewport = arguments[0];
                    cx = viewport.center.x;
                    cy = viewport.center.y;
                    zoom = viewport.zoom;
                }
                zoom = zoom || 1;
                box = this.getViewBox();
                var matrix = new Matrix();
                var dx = box.x + box.width / 2 - cx, dy = box.y + box.height / 2 - cy;
                matrix.translate(-cx, -cy);
                matrix.scale(zoom);
                matrix.translate(cx, cy);
                matrix.translate(dx, dy);
                this.shapeNode.setAttribute("transform", "matrix(" + matrix + ")");
                this.viewport = {
                    center: {
                        x: cx,
                        y: cy
                    },
                    offset: {
                        x: dx,
                        y: dy
                    },
                    zoom: zoom
                };
                return this;
            },
            getViewPort: function() {
                if (!this.viewport) {
                    var box = this.getViewBox();
                    return {
                        zoom: 1,
                        center: {
                            x: box.x + box.width / 2,
                            y: box.y + box.height / 2
                        },
                        offset: {
                            x: 0,
                            y: 0
                        }
                    };
                }
                return this.viewport;
            },
            getViewPortMatrix: function() {
                return Matrix.parse(this.shapeNode.getAttribute("transform"));
            },
            getViewPortTransform: function() {
                var m = this.shapeNode.getCTM();
                return new Matrix(m.a, m.b, m.c, m.d, m.e, m.f);
            },
            getTransform: function() {
                return this.getViewPortTransform().reverse();
            },
            addResource: function(resource) {
                this.resources.appendItem(resource);
                if (resource.node) {
                    this.resourceNode.appendChild(resource.node);
                }
                return this;
            },
            removeResource: function(resource) {
                if (resource.remove) {
                    resource.remove();
                }
                if (resource.node) {
                    this.resourceNode.removeChild(resource.node);
                }
                return this;
            },
            getPaper: function() {
                return this;
            }
        });
        var Shape = _p.r(60);
        Class.extendClass(Shape, {
            getPaper: function() {
                var parent = this.container;
                while (parent && parent instanceof Paper === false) {
                    parent = parent.container;
                }
                return parent;
            },
            isAttached: function() {
                return !!this.getPaper();
            },
            whenPaperReady: function(fn) {
                var me = this;
                function check() {
                    var paper = me.getPaper();
                    if (paper && fn) {
                        fn.call(me, paper);
                    }
                    return paper;
                }
                if (!check()) {
                    this.on("add treeadd", function listen() {
                        if (check()) {
                            me.off("add", listen);
                            me.off("treeadd", listen);
                        }
                    });
                }
                return this;
            }
        });
        return Paper;
    }
};

//src/graphic/path.js
_p[46] = {
    value: function(require, exports, module) {
        var Utils = _p.r(12);
        var createClass = _p.r(11).createClass;
        var Shape = _p.r(60);
        var svg = _p.r(67);
        var g = _p.r(34);
        var slice = Array.prototype.slice, flatten = Utils.flatten;
        var PathDrawer = createClass("PathDrawer", {
            constructor: function(path) {
                this.segment = [];
                this.path = path;
                this.__clear = false;
            },
            getPath: function() {
                return this.path;
            },
            redraw: function() {
                this._transation = this._transation || [];
                return this.clear();
            },
            done: function() {
                var transation = this._transation;
                this._transation = null;
                this.push(transation);
                return this;
            },
            clear: function() {
                if (this._transation) {
                    this._transation = [];
                } else {
                    this.path.setPathData("M 0 0");
                }
                this._clear = true;
                return this;
            },
            push: function() {
                var segment = slice.call(arguments);
                var originData;
                if (this._transation) {
                    this._transation.push(segment);
                    return this;
                }
                if (this._clear) {
                    originData = "";
                    this._clear = false;
                } else {
                    originData = this.path.getPathData();
                }
                originData = originData || "";
                this.path.setPathData(originData + g.pathToString(segment));
                return this;
            },
            moveTo: function(x, y) {
                return this.push("M", slice.call(arguments));
            },
            moveBy: function(dx, dy) {
                return this.push("m", slice.call(arguments));
            },
            lineTo: function(x, y) {
                return this.push("L", slice.call(arguments));
            },
            lineBy: function(dx, dy) {
                return this.push("l", slice.call(arguments));
            },
            arcTo: function(rx, ry, xr, laf, sf, x, y) {
                return this.push("A", slice.call(arguments));
            },
            arcBy: function(rx, ry, xr, laf, sf, dx, dy) {
                return this.push("a", arguments);
            },
            carcTo: function(r, laf, sf, x, y) {
                return this.push("A", [ r, r, 0 ].concat(slice.call(arguments, 1)));
            },
            carcBy: function(r, laf, sf, dx, dy) {
                return this.push("a", [ r, r, 0 ].concat(slice.call(arguments, 1)));
            },
            bezierTo: function(x1, y1, x2, y2, x, y) {
                return this.push("C", slice.call(arguments));
            },
            bezierBy: function(dx1, dy1, dx2, dy2, dx, dy) {
                return this.push("c", slice.call(arguments));
            },
            close: function() {
                return this.push("z");
            }
        });
        return createClass("Path", {
            base: Shape,
            constructor: function(data) {
                this.callBase("path");
                if (data) {
                    this.setPathData(data);
                }
                this.node.setAttribute("fill", svg.defaults.fill);
                this.node.setAttribute("stroke", svg.defaults.stroke);
            },
            setPathData: function(data) {
                data = data || "M0,0";
                this.pathdata = g.pathToString(data);
                this.node.setAttribute("d", this.pathdata);
                this.trigger("shapeupdate", {
                    type: "pathdata"
                });
                return this;
            },
            getPathData: function() {
                return this.pathdata || "";
            },
            getDrawer: function() {
                return new PathDrawer(this);
            },
            isClosed: function() {
                var data = this.getPathData();
                return !!~data.indexOf("z") || !!~data.indexOf("Z");
            }
        });
    }
};

//src/graphic/pattern.js
_p[47] = {
    value: function(require, exports, module) {
        var Resource = _p.r(58);
        var ShapeContainer = _p.r(61);
        var svg = _p.r(67);
        return _p.r(11).createClass("PatternBrush", {
            base: Resource,
            mixins: [ ShapeContainer ],
            constructor: function(paper) {
                this.callBase("pattern", paper);
                this.node.setAttribute("patternUnits", "userSpaceOnUse");
            },
            setX: function(x) {
                this.x = x;
                this.node.setAttribute("x", x);
                return this;
            },
            setY: function(y) {
                this.y = y;
                this.node.setAttribute("y", y);
                return this;
            },
            setWidth: function(width) {
                this.width = width;
                this.node.setAttribute("width", width);
                return this;
            },
            setHeight: function(height) {
                this.height = height;
                this.node.setAttribute("height", height);
                return this;
            },
            getWidth: function() {
                return this.width;
            },
            getHeight: function() {
                return this.height;
            }
        });
    }
};

//src/graphic/pen.js
_p[48] = {
    value: function(require, exports, module) {
        var Color = _p.r(28);
        return _p.r(11).createClass("Pen", {
            constructor: function(brush, width) {
                this.brush = brush;
                this.width = width || 1;
                this.linecap = null;
                this.linejoin = null;
                this.dashArray = null;
                this.opacity = 1;
            },
            getBrush: function() {
                return this.brush;
            },
            setBrush: function(brush) {
                this.brush = brush;
                return this;
            },
            setColor: function(color) {
                return this.setBrush(color);
            },
            getColor: function() {
                return this.brush instanceof Color ? this.brush : null;
            },
            getWidth: function() {
                return this.width;
            },
            setWidth: function(width) {
                this.width = width;
                return this;
            },
            getOpacity: function() {
                return this.opacity;
            },
            setOpacity: function(opacity) {
                this.opacity = opacity;
            },
            getLineCap: function() {
                return this.linecap;
            },
            setLineCap: function(linecap) {
                this.linecap = linecap;
                return this;
            },
            getLineJoin: function() {
                return this.linejoin;
            },
            setLineJoin: function(linejoin) {
                this.linejoin = linejoin;
                return this;
            },
            getDashArray: function() {
                return this.dashArray;
            },
            setDashArray: function(dashArray) {
                this.dashArray = dashArray;
                return this;
            },
            stroke: function(shape) {
                var node = shape.node;
                node.setAttribute("stroke", this.brush.toString());
                node.setAttribute("stroke-width", this.getWidth());
                if (this.getOpacity() < 1) {
                    node.setAttribute("stroke-opacity", this.getOpacity());
                }
                if (this.getLineCap()) {
                    node.setAttribute("stroke-linecap", this.getLineCap());
                }
                if (this.getLineJoin()) {
                    node.setAttribute("stroke-linejoin", this.getLineJoin());
                }
                if (this.getDashArray()) {
                    node.setAttribute("stroke-dasharray", this.getDashArray());
                }
            }
        });
    }
};

//src/graphic/pie.js
_p[49] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass({
            base: _p.r(68),
            constructor: function(radius, angle, angleOffset) {
                this.callBase([ 0, radius ], angle, angleOffset);
            },
            getRadius: function() {
                return this.getSectionArray()[1];
            },
            setRadius: function(radius) {
                this.setSectionArray([ 0, radius ]);
            }
        });
    }
};

//src/graphic/point.js
/*
 * 点对象抽象
 */
_p[50] = {
    value: function(require, exports, module) {
        /**
     * @class kity.Point
     * @description 表示一个点
     */
        var Point = _p.r(11).createClass("Point", {
            /**
         * @constructor
         * @for kity.Point
         * @description 指定默认的 x 和 y 创建一个点
         * 
         * @param  {Number} x 点的 x 坐标
         * @param  {Number} y 点的 y 坐标
         */
            constructor: function(x, y) {
                /**
             * @property
             * @for kity.Point
             * @description 表示点的 x 坐标
             * @type {Number}
             */
                this.x = x || 0;
                /**
             * @property
             * @for kity.Point
             * @description 表示点的 y 坐标
             * @type {Number}
             */
                this.y = y || 0;
            },
            offset: function(dx, dy) {
                if (arguments.length == 1) {
                    dy = dx.y;
                    dx = dx.x;
                }
                return new Point(this.x + dx, this.y + dy);
            },
            valueOf: function() {
                return [ this.x, this.y ];
            },
            toString: function() {
                return this.valueOf().join(" ");
            },
            spof: function() {
                return new Point((this.x | 0) + .5, (this.y | 0) + .5);
            },
            round: function() {
                return new Point(this.x | 0, this.y | 0);
            },
            isOrigin: function() {
                return this.x === 0 && this.y === 0;
            }
        });
        /**
     * @static
     * @method fromPolar()
     * @for kity.Point
     * @grammar kity.Point.fromPolar(radius, angle, unit) => kity.Point
     * @param  {Number} radius 极坐标中的半径
     * @param  {Number} angle  极坐标中的角度
     * @param  {String} unit   角度使用的单位，默认为 'deg' (角度)，可以取值为 'rad'，表示传入的是弧度值
     */
        Point.fromPolar = function(radius, angle, unit) {
            if (unit != "rad") {
                // deg to rad
                angle = angle / 180 * Math.PI;
            }
            return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
        };
        Point.parse = function(unknown) {
            if (!unknown) return new Point();
            if (unknown instanceof Point) {
                return unknown;
            }
            if (typeof unknown == "string") {
                return Point.parse(unknown.split(/\s*[\s,]\s*/));
            }
            if ("0" in unknown && "1" in unknown) {
                return new Point(unknown[0], unknown[1]);
            }
        };
        return Point;
    }
};

//src/graphic/pointcontainer.js
/**
 * 点集合容器
 */
_p[51] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("PointContainer", {
            base: _p.r(29),
            constructor: function() {
                this.callBase();
            },
            addPoint: function(point, pos) {
                return this.addItem.apply(this, arguments);
            },
            prependPoint: function() {
                return this.prependItem.apply(this, arguments);
            },
            appendPoint: function() {
                return this.appendItem.apply(this, arguments);
            },
            removePoint: function(pos) {
                return this.removeItem.apply(this, arguments);
            },
            addPoints: function() {
                return this.addItems.apply(this, arguments);
            },
            setPoints: function() {
                return this.setItems.apply(this, arguments);
            },
            getPoint: function() {
                return this.getItem.apply(this, arguments);
            },
            getPoints: function() {
                return this.getItems.apply(this, arguments);
            },
            getFirstPoint: function() {
                return this.getFirstItem.apply(this, arguments);
            },
            getLastPoint: function() {
                return this.getLastItem.apply(this, arguments);
            }
        });
    }
};

//src/graphic/poly.js
/*
 * 通过点来决定图形的公共父类
 */
_p[52] = {
    value: function(require, exports, module) {
        var Utils = _p.r(12);
        return _p.r(11).createClass("Poly", {
            base: _p.r(46),
            mixins: [ _p.r(51) ],
            constructor: function(points, closeable) {
                this.callBase();
                //是否可闭合
                this.closeable = !!closeable;
                this.setPoints(points || []);
                this.changeable = true;
                this.update();
            },
            //当点集合发生变化时采取的动作
            onContainerChanged: function() {
                if (this.changeable) {
                    this.update();
                }
            },
            update: function() {
                var drawer = this.getDrawer(), points = this.getPoints();
                drawer.clear();
                if (!points.length) {
                    return this;
                }
                drawer.moveTo(points[0]);
                for (var i = 1, point, len = points.length; i < len; i++) {
                    point = points[i];
                    drawer.lineTo(point);
                }
                if (this.closeable && points.length > 2) {
                    drawer.close();
                }
                return this;
            }
        });
    }
};

//src/graphic/polygon.js
_p[53] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("Polygon", {
            base: _p.r(52),
            constructor: function(points) {
                this.callBase(points, true);
            }
        });
    }
};

//src/graphic/polyline.js
_p[54] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("Polyline", {
            base: _p.r(52),
            constructor: function(points) {
                this.callBase(points);
            }
        });
    }
};

//src/graphic/radialgradient.js
_p[55] = {
    value: function(require, exports, module) {
        var Gradient = _p.r(35);
        return _p.r(11).createClass("RadialGradientBrush", {
            base: Gradient,
            constructor: function(paper) {
                this.callBase("radialGradient", paper);
                this.setCenter(.5, .5);
                this.setFocal(.5, .5);
                this.setRadius(.5);
            },
            setCenter: function(cx, cy) {
                this.node.setAttribute("cx", cx);
                this.node.setAttribute("cy", cy);
                return this;
            },
            getCenter: function() {
                return {
                    x: +this.node.getAttribute("cx"),
                    y: +this.node.getAttribute("cy")
                };
            },
            setFocal: function(fx, fy) {
                this.node.setAttribute("fx", fx);
                this.node.setAttribute("fy", fy);
                return this;
            },
            getFocal: function() {
                return {
                    x: +this.node.getAttribute("fx"),
                    y: +this.node.getAttribute("fy")
                };
            },
            setRadius: function(r) {
                this.node.setAttribute("r", r);
                return this;
            },
            getRadius: function() {
                return +this.node.getAttribute("r");
            }
        });
    }
};

//src/graphic/rect.js
_p[56] = {
    value: function(require, exports, module) {
        var RectUtils = {}, Utils = _p.r(12), Point = _p.r(50), Box = _p.r(25);
        Utils.extend(RectUtils, {
            //根据传递进来的width、height和radius属性，
            //获取最适合的radius值
            formatRadius: function(width, height, radius) {
                var minValue = Math.floor(Math.min(width / 2, height / 2));
                return Math.min(minValue, radius);
            }
        });
        /**
     * @class kity.Rect
     * @description 表示一个矩形
     * @base kity.Path
     */
        var Rect = _p.r(11).createClass("Rect", {
            base: _p.r(46),
            /**
         * @constructor
         * @for kity.Rect
         * @grammar new kity.Rect(width, height, x, y, radius)
         * @param  {Number} width  矩形的初始化宽度
         * @param  {Number} height 矩形的初始化高度
         * @param  {Number} x      矩形的初始化 x 坐标
         * @param  {Number} y      矩形的初始化 y 坐标
         * @param  {Number} radius 矩形的初始化圆角大小
         */
            constructor: function(width, height, x, y, radius) {
                this.callBase();
                this.x = x || 0;
                this.y = y || 0;
                this.width = width || 0;
                this.height = height || 0;
                this.radius = RectUtils.formatRadius(this.width, this.height, radius || 0);
                this.update();
            },
            update: function() {
                var x = this.x, y = this.y, w = this.width, h = this.height, r = this.radius;
                var drawer = this.getDrawer().redraw();
                if (!r) {
                    // 直角
                    drawer.push("M", x, y);
                    drawer.push("h", w);
                    drawer.push("v", h);
                    drawer.push("h", -w);
                    drawer.push("z");
                } else {
                    //圆角
                    w -= 2 * r;
                    h -= 2 * r;
                    drawer.push("M", x + r, y);
                    drawer.push("h", w);
                    drawer.push("a", r, r, 0, 0, 1, r, r);
                    drawer.push("v", h);
                    drawer.push("a", r, r, 0, 0, 1, -r, r);
                    drawer.push("h", -w);
                    drawer.push("a", r, r, 0, 0, 1, -r, -r);
                    drawer.push("v", -h);
                    drawer.push("a", r, r, 0, 0, 1, r, -r);
                    drawer.push("z");
                }
                drawer.done();
                return this;
            },
            /**
         * @method setWidth
         * @for kity.Rect
         * @grammar setWidth(width) => kity.Rect
         * @description 设置矩形的宽度，设置后返回矩形实例本身
         * @param {Number} width 宽度值
         *
         * @example
         * ```js
         * rect.setWidth(300);
         * ```
         */
            setWidth: function(width) {
                this.width = width;
                return this.update();
            },
            /**
         * @method setHeight
         * @for  kity.Rect
         * @grammar setHeight(height) => kity.Rect
         * @description 设置矩形的高度，设置后返回矩形实例本身
         * @param {Number} height 高度值
         *
         * @example
         * ```js
         * rect.setHeight(200);
         * ```
         */
            setHeight: function(height) {
                this.height = height;
                return this.update();
            },
            /**
         * @method setSize
         * @for  kity.Rect
         * @grammar setSize(width, height) => kity.Rect
         * @description 设置矩形的尺寸，设置后返回矩形本身
         * @param {Number} width  矩形的宽度值
         * @param {Number} height 矩形的高度值
         *
         * @example
         * ```js
         * rect.setSize(300, 200);
         * ```
         */
            setSize: function(width, height) {
                this.width = width;
                this.height = height;
                return this.update();
            },
            /**
         * @method setBox
         * @for kity.Rect
         * @grammar setBox(box) => kity.Rect
         * @description 使用一个 kity 的盒子数据，
         * @param {kity.Box} box 盒子数据
         */
            setBox: function(box) {
                this.x = box.x;
                this.y = box.y;
                this.width = box.width;
                this.height = box.height;
                return this.update();
            },
            getBox: function() {
                return new Box(this.x, this.y, this.width, this.height);
            },
            getRadius: function() {
                return this.radius;
            },
            setRadius: function(radius) {
                this.radius = RectUtils.formatRadius(this.width, this.height, radius || 0);
                return this.update();
            },
            getPosition: function() {
                return new Point(this.x, this.y);
            },
            setPosition: function(x, y) {
                if (arguments.length == 1) {
                    var p = Point.parse(arguments[0]);
                    y = p.y;
                    x = p.x;
                }
                this.x = x;
                this.y = y;
                return this.update();
            },
            getWidth: function() {
                return this.width;
            },
            getHeight: function() {
                return this.height;
            },
            getPositionX: function() {
                return this.x;
            },
            getPositionY: function() {
                return this.y;
            },
            setPositionX: function(x) {
                this.x = x;
                return this.update();
            },
            setPositionY: function(y) {
                this.y = y;
                return this.update();
            }
        });
        return Rect;
    }
};

//src/graphic/regularpolygon.js
_p[57] = {
    value: function(require, exports, module) {
        var Point = _p.r(50);
        return _p.r(11).createClass("RegularPolygon", {
            base: _p.r(46),
            constructor: function(side, radius, x, y) {
                this.callBase();
                this.radius = radius || 0;
                this.side = Math.max(side || 3, 3);
                if (arguments.length > 2) {
                    if (arguments.length == 3) {
                        y = x.y;
                        x = x.x;
                    }
                }
                this.center = new Point(x, y);
                this.draw();
            },
            getSide: function() {
                return this.side;
            },
            setSide: function(side) {
                this.side = side;
                return this.draw();
            },
            getRadius: function() {
                return this.radius;
            },
            setRadius: function(radius) {
                this.radius = radius;
                return this.draw();
            },
            draw: function() {
                var radius = this.radius, side = this.side, step = Math.PI * 2 / side, drawer = this.getDrawer(), i;
                drawer.clear();
                drawer.moveTo(Point.fromPolar(radius, Math.PI / 2, "rad").offset(this.center));
                for (i = 0; i <= side; i++) {
                    drawer.lineTo(Point.fromPolar(radius, step * i + Math.PI / 2, "rad").offset(this.center));
                }
                drawer.close();
                return this;
            }
        });
    }
};

//src/graphic/resource.js
/**
 * @fileOverview
 *
 * 资源节点基类
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
_p[58] = {
    value: function(require, exports, module) {
        var svg = _p.r(67);
        return _p.r(11).createClass("Resource", {
            constructor: function(nodeType, paper) {
                this.callBase();
                this.node = svg.createNode(nodeType);
                if (paper) {
                    paper.addResource(this);
                }
            },
            toString: function() {
                return "url(#" + this.node.id + ")";
            }
        });
    }
};

//src/graphic/ring.js
_p[59] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass({
            base: _p.r(68),
            constructor: function(innerRadius, outerRadius) {
                this.callBase([ innerRadius, outerRadius ], 360, 0);
            },
            getInnerRadius: function() {
                return this.getSectionArray()[0];
            },
            getOuterRadius: function() {
                return this.getSectionArray()[1];
            },
            setInnerRadius: function(value) {
                this.setSectionArray([ value, this.getOuterRadius() ]);
            },
            setOuterRadius: function(value) {
                this.setSectionArray([ this.getInnerRadius(), value ]);
            }
        });
    }
};

//src/graphic/shape.js
_p[60] = {
    value: function(require, exports, module) {
        var svg = _p.r(67);
        var utils = _p.r(12);
        var EventHandler = _p.r(33);
        var Styled = _p.r(66);
        var Data = _p.r(31);
        var Matrix = _p.r(43);
        var Pen = _p.r(48);
        var slice = Array.prototype.slice;
        var Box = _p.r(25);
        var Shape = _p.r(11).createClass("Shape", {
            mixins: [ EventHandler, Styled, Data ],
            constructor: function Shape(tagName) {
                this.node = svg.createNode(tagName);
                this.node.shape = this;
                this.transform = {
                    translate: null,
                    rotate: null,
                    scale: null,
                    matrix: null
                };
                this.callMixin();
            },
            getId: function() {
                return this.node.id;
            },
            setId: function(id) {
                this.node.id = id;
                return this;
            },
            getNode: function() {
                return this.node;
            },
            getBoundaryBox: function() {
                var box;
                try {
                    box = this.node.getBBox();
                } catch (e) {
                    box = {
                        x: this.node.clientLeft,
                        y: this.node.clientTop,
                        width: this.node.clientWidth,
                        height: this.node.clientHeight
                    };
                }
                return new Box(box);
            },
            getRenderBox: function(refer) {
                var box = this.getBoundaryBox();
                var matrix = this.getTransform(refer);
                return matrix.transformBox(box);
            },
            getWidth: function() {
                return this.getRenderBox().width;
            },
            getHeight: function() {
                return this.getRenderBox().height;
            },
            getSize: function() {
                var box = this.getRenderBox();
                delete box.x;
                delete box.y;
                return box;
            },
            setOpacity: function(value) {
                this.node.setAttribute("opacity", value);
                return this;
            },
            getOpacity: function() {
                var opacity = this.node.getAttribute("opacity");
                return opacity ? +opacity : 1;
            },
            setVisible: function(value) {
                if (value) {
                    this.node.removeAttribute("display");
                } else {
                    this.node.setAttribute("display", "none");
                }
                return this;
            },
            getVisible: function() {
                this.node.getAttribute("display");
            },
            hasAncestor: function(node) {
                var parent = this.container;
                while (parent) {
                    if (parent === node) {
                        return true;
                    }
                    parent = parent.container;
                }
                return false;
            },
            getTransform: function(refer) {
                return Matrix.getCTM(this, refer);
            },
            clearTransform: function() {
                this.node.removeAttribute("transform");
                this.transform = {
                    translate: null,
                    rotate: null,
                    scale: null,
                    matrix: null
                };
                this.trigger("shapeupdate", {
                    type: "transform"
                });
                return this;
            },
            _applyTransform: function() {
                var t = this.transform, result = [];
                if (t.translate) {
                    result.push([ "translate(", t.translate, ")" ]);
                }
                if (t.rotate) {
                    result.push([ "rotate(", t.rotate, ")" ]);
                }
                if (t.scale) {
                    result.push([ "scale(", t.scale, ")" ]);
                }
                if (t.matrix) {
                    result.push([ "matrix(", t.matrix, ")" ]);
                }
                this.node.setAttribute("transform", utils.flatten(result).join(" "));
                return this;
            },
            setMatrix: function(m) {
                this.transform.matrix = m;
                return this._applyTransform();
            },
            setTranslate: function(t) {
                this.transform.translate = t !== null && slice.call(arguments) || null;
                return this._applyTransform();
            },
            setRotate: function(r) {
                this.transform.rotate = r !== null && slice.call(arguments) || null;
                return this._applyTransform();
            },
            setScale: function(s) {
                this.transform.scale = s !== null && slice.call(arguments) || null;
                return this._applyTransform();
            },
            translate: function(dx, dy) {
                var m = this.transform.matrix || new Matrix();
                if (dy === undefined) {
                    dy = 0;
                }
                this.transform.matrix = m.translate(dx, dy);
                return this._applyTransform();
            },
            rotate: function(deg) {
                var m = this.transform.matrix || new Matrix();
                this.transform.matrix = m.rotate(deg);
                return this._applyTransform();
            },
            scale: function(sx, sy) {
                var m = this.transform.matrix || new Matrix();
                if (sy === undefined) {
                    sy = sx;
                }
                this.transform.matrix = m.scale(sx, sy);
                return this._applyTransform();
            },
            skew: function(sx, sy) {
                var m = this.transform.matrix || new Matrix();
                if (sy === undefined) {
                    sy = sx;
                }
                this.transform.matrix = m.skew(sx, sy);
                return this._applyTransform();
            },
            stroke: function(pen, width) {
                if (pen && pen.stroke) {
                    pen.stroke(this);
                } else if (pen) {
                    // 字符串或重写了 toString 的对象
                    this.node.setAttribute("stroke", pen.toString());
                    if (width) {
                        this.node.setAttribute("stroke-width", width);
                    }
                } else if (pen === null) {
                    this.node.removeAttribute("stroe");
                }
                return this;
            },
            fill: function(brush) {
                // 字符串或重写了 toString 的对象
                if (brush) {
                    this.node.setAttribute("fill", brush.toString());
                }
                if (brush === null) {
                    this.node.removeAttribute("fill");
                }
                return this;
            },
            setAttr: function(a, v) {
                var me = this;
                if (utils.isObject(a)) {
                    utils.each(a, function(val, key) {
                        me.setAttr(key, val);
                    });
                }
                if (v === undefined || v === null || v === "") {
                    this.node.removeAttribute(a);
                } else {
                    this.node.setAttribute(a, v);
                }
                return this;
            },
            getAttr: function(a) {
                return this.node.getAttribute(a);
            }
        });
        return Shape;
    }
};

//src/graphic/shapecontainer.js
_p[61] = {
    value: function(require, exports, module) {
        var Container = _p.r(29);
        var utils = _p.r(12);
        var ShapeContainer = _p.r(11).createClass("ShapeContainer", {
            base: Container,
            isShapeContainer: true,
            /* private */
            handleAdd: function(shape, index) {
                var parent = this.getShapeNode();
                parent.insertBefore(shape.node, parent.childNodes[index] || null);
                shape.trigger("add", {
                    container: this
                });
                if (shape.notifyTreeModification) {
                    shape.notifyTreeModification("treeadd", this);
                }
            },
            /* private */
            handleRemove: function(shape, index) {
                var parent = this.getShapeNode();
                parent.removeChild(shape.node);
                shape.trigger("remove", {
                    container: this
                });
                if (shape.notifyTreeModification) {
                    shape.notifyTreeModification("treeremove", this);
                }
            },
            /* private */
            notifyTreeModification: function(type, container) {
                this.eachItem(function(index, shape) {
                    if (shape.notifyTreeModification) {
                        shape.notifyTreeModification(type, container);
                    }
                    shape.trigger(type, {
                        container: container
                    });
                });
            },
            /* public */
            getShape: function(index) {
                return this.getItem(index);
            },
            /* public */
            addShape: function(shape, index) {
                return this.addItem(shape, index);
            },
            put: function(shape) {
                this.addShape(shape);
                return shape;
            },
            appendShape: function(shape) {
                return this.addShape(shape);
            },
            prependShape: function(shape) {
                return this.addShape(shape, 0);
            },
            replaceShape: function(replacer, origin) {
                var index = this.indexOf(origin);
                if (index === -1) {
                    return;
                }
                this.removeShape(index);
                this.addShape(replacer, index);
                return this;
            },
            addShapeBefore: function(shape, refer) {
                var index = this.indexOf(refer);
                return this.addShape(shape, index);
            },
            addShapeAfter: function(shape, refer) {
                var index = this.indexOf(refer);
                return this.addShape(shape, index === -1 ? undefined : index + 1);
            },
            /* public */
            addShapes: function(shapes) {
                return this.addItems(shapes);
            },
            /* public */
            removeShape: function(index) {
                return this.removeItem(index);
            },
            getShapes: function() {
                return this.getItems();
            },
            getShapesByType: function(name) {
                var shapes = [];
                function getShapes(shape) {
                    if (name.toLowerCase() == shape.getType().toLowerCase()) {
                        shapes.push(shape);
                    }
                    if (shape.isShapeContainer) {
                        utils.each(shape.getShapes(), function(n) {
                            getShapes(n);
                        });
                    }
                }
                getShapes(this);
                return shapes;
            },
            /* public */
            getShapeById: function(id) {
                return this.getShapeNode().getElementById(id).shape;
            },
            arrangeShape: function(shape, index) {
                return this.removeShape(shape).addShape(shape, index);
            },
            /* protected */
            getShapeNode: function() {
                return this.shapeNode || this.node;
            }
        });
        var Shape = _p.r(60);
        _p.r(11).extendClass(Shape, {
            bringTo: function(index) {
                this.container.arrangeShape(this, index);
                return this;
            },
            bringFront: function() {
                return this.bringTo(this.container.indexOf(this) + 1);
            },
            bringBack: function() {
                return this.bringTo(this.container.indexOf(this) - 1);
            },
            bringTop: function() {
                this.container.removeShape(this).addShape(this);
                return this;
            },
            bringRear: function() {
                return this.bringTo(0);
            },
            bringRefer: function(referShape, offset) {
                if (referShape.container) {
                    if (this.remove) {
                        this.remove();
                    }
                    referShape.container.addShape(this, referShape.container.indexOf(referShape) + (offset || 0));
                }
                return this;
            },
            bringAbove: function(referShape) {
                return this.bringRefer(referShape);
            },
            bringBelow: function(referShape) {
                return this.bringRefer(referShape, 1);
            },
            replaceBy: function(newShape) {
                if (this.container) {
                    newShape.bringAbove(this);
                    this.remove();
                }
                return this;
            }
        });
        return ShapeContainer;
    }
};

//src/graphic/shapeevent.js
/*
 * 图形事件包装类
 * */
_p[62] = {
    value: function(require, exprots, module) {
        var Matrix = _p.r(43), Utils = _p.r(12), Point = _p.r(50);
        return _p.r(11).createClass("ShapeEvent", {
            constructor: function(event) {
                var target = null;
                // dom 事件封装对象
                if (!Utils.isObject(event.target)) {
                    this.type = event.type;
                    target = event.target;
                    // use标签有特殊属性， 需要区别对待
                    if (target.correspondingUseElement) {
                        target = target.correspondingUseElement;
                    }
                    this.originEvent = event;
                    this.targetShape = target.shape || target.paper || event.currentTarget && (event.currentTarget.shape || event.currentTarget.paper);
                    if (event._kityParam) {
                        Utils.extend(this, event._kityParam);
                    }
                } else {
                    Utils.extend(this, event);
                }
            },
            preventDefault: function() {
                var evt = this.originEvent;
                if (!evt) {
                    return true;
                }
                if (evt.preventDefault) {
                    evt.preventDefault();
                    return evt.cancelable;
                } else {
                    evt.returnValue = false;
                    return true;
                }
            },
            //当前鼠标事件在用户坐标系中点击的点的坐标位置
            getPosition: function(refer, touchIndex) {
                if (!this.originEvent) {
                    return null;
                }
                var eventClient = this.originEvent.touches ? this.originEvent.touches[touchIndex || 0] : this.originEvent;
                var target = this.targetShape;
                var targetNode = target.shapeNode || target.node;
                var pScreen = new Point(eventClient && eventClient.clientX || 0, eventClient && eventClient.clientY || 0);
                var pTarget = Matrix.transformPoint(pScreen, targetNode.getScreenCTM().inverse());
                var pRefer = Matrix.getCTM(target, refer || "view").transformPoint(pTarget);
                return pRefer;
            },
            stopPropagation: function() {
                var evt = this.originEvent;
                if (!evt) {
                    return true;
                }
                if (evt.stopPropagation) {
                    evt.stopPropagation();
                } else {
                    evt.cancelBubble = false;
                }
            }
        });
    }
};

//src/graphic/shapepoint.js
/*
 * 图形上的点抽象
 */
_p[63] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("ShapePoint", {
            base: _p.r(50),
            constructor: function(px, py) {
                this.callBase(px, py);
            },
            setX: function(x) {
                return this.setPoint(x, this.y);
            },
            setY: function(y) {
                return this.setPoint(this.x, y);
            },
            setPoint: function(x, y) {
                this.x = x;
                this.y = y;
                this.update();
                return this;
            },
            getPoint: function() {
                return this;
            },
            update: function() {
                if (this.container && this.container.update) {
                    this.container.update();
                }
                return this;
            }
        });
    }
};

//src/graphic/standardcolor.js
/**
 * 标准颜色映射
 */
_p[64] = {
    value: {
        COLOR_STANDARD: {
            aliceblue: "#f0f8ff",
            antiquewhite: "#faebd7",
            aqua: "#00ffff",
            aquamarine: "#7fffd4",
            azure: "#f0ffff",
            beige: "#f5f5dc",
            bisque: "#ffe4c4",
            black: "#000000",
            blanchedalmond: "#ffebcd",
            blue: "#0000ff",
            blueviolet: "#8a2be2",
            brown: "#a52a2a",
            burlywood: "#deb887",
            cadetblue: "#5f9ea0",
            chartreuse: "#7fff00",
            chocolate: "#d2691e",
            coral: "#ff7f50",
            cornflowerblue: "#6495ed",
            cornsilk: "#fff8dc",
            crimson: "#dc143c",
            cyan: "#00ffff",
            darkblue: "#00008b",
            darkcyan: "#008b8b",
            darkgoldenrod: "#b8860b",
            darkgray: "#a9a9a9",
            darkgreen: "#006400",
            darkgrey: "#a9a9a9",
            darkkhaki: "#bdb76b",
            darkmagenta: "#8b008b",
            darkolivegreen: "#556b2f",
            darkorange: "#ff8c00",
            darkorchid: "#9932cc",
            darkred: "#8b0000",
            darksalmon: "#e9967a",
            darkseagreen: "#8fbc8f",
            darkslateblue: "#483d8b",
            darkslategray: "#2f4f4f",
            darkslategrey: "#2f4f4f",
            darkturquoise: "#00ced1",
            darkviolet: "#9400d3",
            deeppink: "#ff1493",
            deepskyblue: "#00bfff",
            dimgray: "#696969",
            dimgrey: "#696969",
            dodgerblue: "#1e90ff",
            firebrick: "#b22222",
            floralwhite: "#fffaf0",
            forestgreen: "#228b22",
            fuchsia: "#ff00ff",
            gainsboro: "#dcdcdc",
            ghostwhite: "#f8f8ff",
            gold: "#ffd700",
            goldenrod: "#daa520",
            gray: "#808080",
            green: "#008000",
            greenyellow: "#adff2f",
            grey: "#808080",
            honeydew: "#f0fff0",
            hotpink: "#ff69b4",
            indianred: "#cd5c5c",
            indigo: "#4b0082",
            ivory: "#fffff0",
            khaki: "#f0e68c",
            lavender: "#e6e6fa",
            lavenderblush: "#fff0f5",
            lawngreen: "#7cfc00",
            lemonchiffon: "#fffacd",
            lightblue: "#add8e6",
            lightcoral: "#f08080",
            lightcyan: "#e0ffff",
            lightgoldenrodyellow: "#fafad2",
            lightgray: "#d3d3d3",
            lightgreen: "#90ee90",
            lightgrey: "#d3d3d3",
            lightpink: "#ffb6c1",
            lightsalmon: "#ffa07a",
            lightseagreen: "#20b2aa",
            lightskyblue: "#87cefa",
            lightslategray: "#778899",
            lightslategrey: "#778899",
            lightsteelblue: "#b0c4de",
            lightyellow: "#ffffe0",
            lime: "#00ff00",
            limegreen: "#32cd32",
            linen: "#faf0e6",
            magenta: "#ff00ff",
            maroon: "#800000",
            mediumaquamarine: "#66cdaa",
            mediumblue: "#0000cd",
            mediumorchid: "#ba55d3",
            mediumpurple: "#9370db",
            mediumseagreen: "#3cb371",
            mediumslateblue: "#7b68ee",
            mediumspringgreen: "#00fa9a",
            mediumturquoise: "#48d1cc",
            mediumvioletred: "#c71585",
            midnightblue: "#191970",
            mintcream: "#f5fffa",
            mistyrose: "#ffe4e1",
            moccasin: "#ffe4b5",
            navajowhite: "#ffdead",
            navy: "#000080",
            oldlace: "#fdf5e6",
            olive: "#808000",
            olivedrab: "#6b8e23",
            orange: "#ffa500",
            orangered: "#ff4500",
            orchid: "#da70d6",
            palegoldenrod: "#eee8aa",
            palegreen: "#98fb98",
            paleturquoise: "#afeeee",
            palevioletred: "#db7093",
            papayawhip: "#ffefd5",
            peachpuff: "#ffdab9",
            peru: "#cd853f",
            pink: "#ffc0cb",
            plum: "#dda0dd",
            powderblue: "#b0e0e6",
            purple: "#800080",
            red: "#ff0000",
            rosybrown: "#bc8f8f",
            royalblue: "#4169e1",
            saddlebrown: "#8b4513",
            salmon: "#fa8072",
            sandybrown: "#f4a460",
            seagreen: "#2e8b57",
            seashell: "#fff5ee",
            sienna: "#a0522d",
            silver: "#c0c0c0",
            skyblue: "#87ceeb",
            slateblue: "#6a5acd",
            slategray: "#708090",
            slategrey: "#708090",
            snow: "#fffafa",
            springgreen: "#00ff7f",
            steelblue: "#4682b4",
            tan: "#d2b48c",
            teal: "#008080",
            thistle: "#d8bfd8",
            tomato: "#ff6347",
            turquoise: "#40e0d0",
            violet: "#ee82ee",
            wheat: "#f5deb3",
            white: "#ffffff",
            whitesmoke: "#f5f5f5",
            yellow: "#ffff00"
        },
        //标准扩展
        EXTEND_STANDARD: {}
    }
};

//src/graphic/star.js
_p[65] = {
    value: function(require, exports, module) {
        /**
     * @see http://www.jdawiseman.com/papers/easymath/surds_star_inner_radius.html
     */
        var defaultRatioForStar = {
            "3": .2,
            // yy
            "5": .38196601125,
            "6": .57735026919,
            "8": .541196100146,
            "10": .726542528005,
            "12": .707106781187
        };
        var Point = _p.r(50);
        return _p.r(11).createClass("Star", {
            base: _p.r(46),
            constructor: function(vertex, radius, shrink, offset, angleOffset) {
                this.callBase();
                this.vertex = vertex || 3;
                this.radius = radius || 0;
                this.shrink = shrink;
                this.offset = offset || new Point(0, 0);
                this.angleOffset = angleOffset || 0;
                this.draw();
            },
            getVertex: function() {
                return this.vertex;
            },
            setVertex: function(value) {
                this.vertex = value;
                return this.draw();
            },
            getRadius: function() {
                return this.radius;
            },
            setRadius: function(value) {
                this.radius = value;
                return this.draw();
            },
            getShrink: function() {
                return this.shrink;
            },
            setShrink: function(value) {
                this.shrink = value;
                return this.draw();
            },
            getOffset: function() {
                return this.offset;
            },
            setOffset: function(value) {
                this.offset = value;
                return this.draw();
            },
            getAngleOffset: function() {
                return this.angleOffset;
            },
            setAngleOffset: function(value) {
                this.angleOffset = value;
                return this.draw();
            },
            draw: function() {
                var innerRadius = this.radius, outerRadius = this.radius * (this.shrink || defaultRatioForStar[this.vertex] || .5), vertex = this.vertex, offset = this.offset, angleStart = 90, angleStep = 180 / vertex, angleOffset = this.angleOffset, drawer = this.getDrawer(), i, angle;
                drawer.clear();
                drawer.moveTo(Point.fromPolar(outerRadius, angleStart));
                for (i = 1; i <= vertex * 2; i++) {
                    angle = angleStart + angleStep * i;
                    // 绘制内点
                    if (i % 2) {
                        drawer.lineTo(Point.fromPolar(innerRadius, angle + angleOffset).offset(offset));
                    } else {
                        drawer.lineTo(Point.fromPolar(outerRadius, angle));
                    }
                }
                drawer.close();
            }
        });
    }
};

//src/graphic/styled.js
_p[66] = {
    value: function(require, exports, module) {
        // polyfill for ie
        var ClassList = _p.r(11).createClass("ClassList", {
            constructor: function(node) {
                this._node = node;
                this._list = node.className.toString().split(" ");
            },
            _update: function() {
                this._node.className = this._list.join(" ");
            },
            add: function(name) {
                this._list.push(name);
                this._update();
            },
            remove: function(name) {
                var index = this._list.indexOf(name);
                if (~index) {
                    this._list.splice(index, 1);
                }
                this._update();
            },
            contains: function(name) {
                return !!~this._list.indexOf(name);
            }
        });
        function getClassList(node) {
            if (!node.classList) {
                node.classList = new ClassList(node);
            }
            return node.classList;
        }
        return _p.r(11).createClass("Styled", {
            addClass: function(name) {
                getClassList(this.node).add(name);
                return this;
            },
            removeClass: function(name) {
                getClassList(this.node).remove(name);
                return this;
            },
            hasClass: function(name) {
                return getClassList(this.node).contains(name);
            },
            setStyle: function(styles) {
                if (arguments.length == 2) {
                    this.node.style[arguments[0]] = arguments[1];
                    return this;
                }
                for (var name in styles) {
                    if (styles.hasOwnProperty(name)) {
                        this.node.style[name] = styles[name];
                    }
                }
                return this;
            }
        });
    }
};

//src/graphic/svg.js
_p[67] = {
    value: function(require, exports, module) {
        var doc = document;
        var id = 0;
        var svg = {
            createNode: function(name) {
                var node = doc.createElementNS(svg.ns, name);
                node.id = "kity_" + name + "_" + id++;
                return node;
            },
            defaults: {
                stroke: "none",
                fill: "none"
            },
            xlink: "http://www.w3.org/1999/xlink",
            ns: "http://www.w3.org/2000/svg"
        };
        return svg;
    }
};

//src/graphic/sweep.js
_p[68] = {
    value: function(require, exports, module) {
        var Point = _p.r(50);
        return _p.r(11).createClass("Sweep", {
            base: _p.r(46),
            constructor: function(sectionArray, angle, angleOffset) {
                this.callBase();
                this.sectionArray = sectionArray || [];
                this.angle = angle || 0;
                this.angleOffset = angleOffset || 0;
                this.draw();
            },
            getSectionArray: function() {
                return this.sectionArray;
            },
            setSectionArray: function(value) {
                this.sectionArray = value;
                return this.draw();
            },
            getAngle: function() {
                return this.angle;
            },
            setAngle: function(value) {
                this.angle = value;
                return this.draw();
            },
            getAngleOffset: function() {
                return this.angleOffset;
            },
            setAngleOffset: function(value) {
                this.angleOffset = value;
                return this.draw();
            },
            draw: function() {
                var sectionArray = this.sectionArray, i;
                for (i = 0; i < sectionArray.length; i += 2) {
                    this.drawSection(sectionArray[i], sectionArray[i + 1]);
                }
                return this;
            },
            drawSection: function(from, to) {
                var angleLength = this.angle && (this.angle % 360 ? this.angle % 360 : 360), angleStart = this.angleOffset, angleHalf = angleStart + angleLength / 2, angleEnd = angleStart + angleLength, sweepFlag = angleLength < 0 ? 0 : 1, drawer = this.getDrawer();
                drawer.redraw();
                if (angleLength === 0) {
                    drawer.done();
                    return;
                }
                drawer.moveTo(Point.fromPolar(from, angleStart));
                drawer.lineTo(Point.fromPolar(to, angleStart));
                if (to) {
                    drawer.carcTo(to, 0, sweepFlag, Point.fromPolar(to, angleHalf));
                    drawer.carcTo(to, 0, sweepFlag, Point.fromPolar(to, angleEnd));
                }
                drawer.lineTo(Point.fromPolar(from, angleEnd));
                if (from) {
                    drawer.carcTo(from, 0, sweepFlag, Point.fromPolar(from, angleHalf));
                    drawer.carcTo(from, 0, sweepFlag, Point.fromPolar(from, angleStart));
                }
                drawer.close();
                drawer.done();
            }
        });
    }
};

//src/graphic/text.js
_p[69] = {
    value: function(require, exports, module) {
        var TextContent = _p.r(70);
        var ShapeContainer = _p.r(61);
        var svg = _p.r(67);
        var utils = _p.r(12);
        var offsetHash = {};
        function getTextBoundOffset(text) {
            var font = text._cachedFontHash;
            if (offsetHash[font]) {
                return offsetHash[font];
            }
            var textContent = text.getContent();
            text.setContent("百度Fex");
            var bbox = text.getBoundaryBox(), y = text.getY();
            var topOffset = y - bbox.y + +text.node.getAttribute("dy"), bottomOffset = topOffset - bbox.height;
            text.setContent(textContent);
            return offsetHash[font] = {
                top: topOffset,
                bottom: bottomOffset,
                middle: (topOffset + bottomOffset) / 2
            };
        }
        return _p.r(11).createClass("Text", {
            base: TextContent,
            mixins: [ ShapeContainer ],
            constructor: function(content) {
                this.callBase("text");
                if (content !== undefined) {
                    this.setContent(content);
                }
                this._buildFontHash();
            },
            _buildFontHash: function() {
                var style = window.getComputedStyle(this.node);
                this._cachedFontHash = [ style.fontFamily, style.fontSize, style.fontStretch, style.fontStyle, style.fontVariant, style.fontWeight ].join("-");
            },
            _fontChanged: function(font) {
                var last = this._lastFont;
                var current = utils.extend({}, last, font);
                if (!last) {
                    this._lastFont = font;
                    return true;
                }
                var changed = last.family != current.family || last.size != current.size || last.style != current.style || last.weight != current.weight;
                this._lastFont = current;
                return changed;
            },
            setX: function(x) {
                this.node.setAttribute("x", x);
                return this;
            },
            setPosition: function(x, y) {
                return this.setX(x).setY(y);
            },
            setY: function(y) {
                this.node.setAttribute("y", y);
                return this;
            },
            getX: function() {
                return +this.node.getAttribute("x") || 0;
            },
            getY: function() {
                return +this.node.getAttribute("y") || 0;
            },
            setFont: function(font) {
                this.callBase(font);
                if (this._fontChanged(font)) {
                    this._buildFontHash();
                    this.setVerticalAlign(this.getVerticalAlign());
                }
                return this;
            },
            setTextAnchor: function(anchor) {
                this.node.setAttribute("text-anchor", anchor);
                return this;
            },
            getTextAnchor: function() {
                return this.node.getAttribute("text-anchor") || "start";
            },
            // top/bottom/middle/baseline
            setVerticalAlign: function(align) {
                this.whenPaperReady(function() {
                    var dy;
                    switch (align) {
                      case "top":
                        dy = getTextBoundOffset(this).top;
                        break;

                      case "bottom":
                        dy = getTextBoundOffset(this).bottom;
                        break;

                      case "middle":
                        dy = getTextBoundOffset(this).middle;
                        break;

                      default:
                        dy = 0;
                    }
                    this.node.setAttribute("dy", dy);
                });
                this.verticalAlign = align;
                return this;
            },
            getVerticalAlign: function() {
                return this.verticalAlign || "baseline";
            },
            setStartOffset: function(offset) {
                // only for text path
                if (this.shapeNode != this.node) {
                    this.shapeNode.setAttribute("startOffset", offset * 100 + "%");
                }
            },
            addSpan: function(span) {
                this.addShape(span);
                return this;
            },
            setPath: function(path) {
                var textpath = this.shapeNode;
                if (this.shapeNode == this.node) {
                    // 当前还不是 textpath
                    textpath = this.shapeNode = svg.createNode("textPath");
                    while (this.node.firstChild) {
                        this.shapeNode.appendChild(this.node.firstChild);
                    }
                    this.node.appendChild(textpath);
                }
                textpath.setAttributeNS(svg.xlink, "xlink:href", "#" + path.node.id);
                this.setTextAnchor(this.getTextAnchor());
                return this;
            }
        });
    }
};

//src/graphic/textcontent.js
_p[70] = {
    value: function(require, exports, module) {
        var Shape = _p.r(60);
        return _p.r(11).createClass("TextContent", {
            base: Shape,
            constructor: function(nodeType) {
                // call shape constructor
                this.callBase(nodeType);
                this.shapeNode = this.shapeNode || this.node;
                this.shapeNode.setAttribute("text-rendering", "geometricPrecision");
            },
            clearContent: function() {
                while (this.shapeNode.firstChild) {
                    this.shapeNode.removeChild(this.shapeNode.firstChild);
                }
                return this;
            },
            setContent: function(content) {
                this.shapeNode.textContent = content;
                return this;
            },
            getContent: function() {
                return this.shapeNode.textContent;
            },
            appendContent: function(content) {
                this.shapeNode.textContent += content;
                return this;
            },
            setSize: function(value) {
                return this.setFontSize(value);
            },
            setFontSize: function(value) {
                return this.setFont({
                    size: value
                });
            },
            setFontFamily: function(value) {
                return this.setFont({
                    family: value
                });
            },
            setFontBold: function(bold) {
                return this.setFont({
                    weight: bold ? "bold" : "normal"
                });
            },
            setFontItalic: function(italic) {
                return this.setFont({
                    style: italic ? "italic" : "normal"
                });
            },
            setFont: function(font) {
                var node = this.node;
                [ "family", "size", "weight", "style" ].forEach(function(section) {
                    if (font[section] === null) {
                        node.removeAttribute("font-" + section);
                    } else if (font[section]) {
                        node.setAttribute("font-" + section, font[section]);
                    }
                });
                return this;
            },
            getExtentOfChar: function(index) {
                return this.node.getExtentOfChar(index);
            },
            getRotationOfChar: function(index) {
                return this.node.getRotationOfChar(index);
            },
            getCharNumAtPosition: function(x, y) {
                return this.node.getCharNumAtPosition(this.node.viewportElement.createSVGPoint(x, y));
            }
        });
    }
};

//src/graphic/textspan.js
_p[71] = {
    value: function(require, exports, module) {
        var TextContent = _p.r(70);
        var Styled = _p.r(66);
        return _p.r(11).createClass("TextSpan", {
            base: TextContent,
            mixins: [ Styled ],
            constructor: function(content) {
                this.callBase("tspan");
                this.setContent(content);
            }
        });
    }
};

//src/graphic/use.js
/*
 * USE 功能
 */
_p[72] = {
    value: function(require, exports, module) {
        var Svg = _p.r(67);
        var Class = _p.r(11);
        var Use = Class.createClass("Use", {
            base: _p.r(60),
            constructor: function(shape) {
                this.callBase("use");
                this.ref(shape);
            },
            ref: function(shape) {
                if (!shape) {
                    this.node.removeAttributeNS(Svg.xlink, "xlink:href");
                    return this;
                }
                var shapeId = shape.getId();
                if (shapeId) {
                    this.node.setAttributeNS(Svg.xlink, "xlink:href", "#" + shapeId);
                }
                // by techird
                // 作为 Use 的图形，如果没有 fill 和 stroke，移除默认的 'none' 值，用于 Use 覆盖
                if (shape.node.getAttribute("fill") === "none") {
                    shape.node.removeAttribute("fill");
                }
                if (shape.node.getAttribute("stroke") === "none") {
                    shape.node.removeAttribute("stroke");
                }
                return this;
            }
        });
        var Shape = _p.r(60);
        Class.extendClass(Shape, {
            // fast-use
            use: function() {
                return new Use(this);
            }
        });
        return Use;
    }
};

//src/graphic/vector.js
_p[73] = {
    value: function(require, exports, module) {
        var Point = _p.r(50);
        var Matrix = _p.r(43);
        var Vector = _p.r(11).createClass("Vector", {
            base: Point,
            constructor: function(x, y) {
                this.callBase(x, y);
            },
            square: function() {
                return this.x * this.x + this.y * this.y;
            },
            length: function() {
                return Math.sqrt(this.square());
            },
            add: function(q) {
                return new Vector(this.x + q.x, this.y + q.y);
            },
            minus: function(q) {
                return new Vector(this.x - q.x, this.y - q.y);
            },
            dot: function(q) {
                return this.x * q.x + this.y * q.y;
            },
            project: function(q) {
                return q.multipy(this.dot(q) / q.square());
            },
            normalize: function(length) {
                if (length === undefined) {
                    length = 1;
                }
                return this.multipy(length / this.length());
            },
            multipy: function(scale) {
                return new Vector(this.x * scale, this.y * scale);
            },
            rotate: function(angle, unit) {
                if (unit == "rad") {
                    angle = angle / Math.PI * 180;
                }
                var p = new Matrix().rotate(angle).transformPoint(this);
                return new Vector(p.x, p.y);
            },
            vertical: function() {
                return new Vector(this.y, -this.x);
            },
            reverse: function() {
                return this.multipy(-1);
            },
            getAngle: function() {
                var length = this.length();
                if (length === 0) return 0;
                var rad = Math.acos(this.x / length);
                var sign = this.y > 0 ? 1 : -1;
                return sign * 180 * rad / Math.PI;
            }
        });
        Vector.fromPoints = function(p1, p2) {
            return new Vector(p2.x - p1.x, p2.y - p1.y);
        };
        Vector.fromPolar = function() {
            var p = Point.fromPolar.apply(Point, arguments);
            return new Vector(p.x, p.y);
        };
        _p.r(11).extendClass(Point, {
            asVector: function() {
                return new Vector(this.x, this.y);
            }
        });
        return Vector;
    }
};

//src/graphic/view.js
_p[74] = {
    value: function(require, exports, module) {
        var ShapeContainer = _p.r(61);
        var ViewBox = _p.r(75);
        return _p.r(11).createClass("View", {
            mixins: [ ShapeContainer, ViewBox ],
            base: _p.r(74),
            constructor: function() {
                this.callBase("view");
            }
        });
    }
};

//src/graphic/viewbox.js
_p[75] = {
    value: function(require, exports, module) {
        return _p.r(11).createClass("ViewBox", {
            getViewBox: function() {
                var attr = this.node.getAttribute("viewBox");
                if (attr === null) {
                    // firefox:
                    // 1. viewBox 没有设置过的时候获得的是 null
                    // 2. svg 标签没有指定绝对大小的时候 clientWidth 和 clientHeigt 为 0，需要在父容器上查找
                    // TODO: 第 2 条取得的不准确（假如有 padding 之类的）
                    return {
                        x: 0,
                        y: 0,
                        width: this.node.clientWidth || this.node.parentNode.clientWidth,
                        height: this.node.clientHeight || this.node.parentNode.clientHeight
                    };
                } else {
                    attr = attr.split(" ");
                    return {
                        x: +attr[0],
                        y: +attr[1],
                        width: +attr[2],
                        height: +attr[3]
                    };
                }
            },
            setViewBox: function(x, y, width, height) {
                this.node.setAttribute("viewBox", [ x, y, width, height ].join(" "));
                return this;
            }
        });
    }
};

//src/kity.js
/**
 * @fileOverview kity 暴露的方法或对象
 */
_p[76] = {
    value: function(require, exports, module) {
        var kity = {}, utils = _p.r(12);
        kity.version = "2.0.0";
        utils.extend(kity, {
            // core
            createClass: _p.r(11).createClass,
            extendClass: _p.r(11).extendClass,
            Utils: utils,
            Browser: _p.r(10),
            // shape
            Box: _p.r(25),
            Bezier: _p.r(23),
            BezierPoint: _p.r(24),
            Circle: _p.r(26),
            Clip: _p.r(27),
            Color: _p.r(28),
            Container: _p.r(29),
            Curve: _p.r(30),
            Ellipse: _p.r(32),
            Group: _p.r(36),
            Gradient: _p.r(35),
            HyperLink: _p.r(37),
            Image: _p.r(38),
            Line: _p.r(39),
            LinearGradient: _p.r(40),
            Mask: _p.r(42),
            Matrix: _p.r(43),
            Marker: _p.r(41),
            Palette: _p.r(44),
            Paper: _p.r(45),
            Path: _p.r(46),
            Pattern: _p.r(47),
            Pen: _p.r(48),
            Point: _p.r(50),
            PointContainer: _p.r(51),
            Polygon: _p.r(53),
            Polyline: _p.r(54),
            Pie: _p.r(49),
            RadialGradient: _p.r(55),
            Resource: _p.r(58),
            Rect: _p.r(56),
            RegularPolygon: _p.r(57),
            Ring: _p.r(59),
            Shape: _p.r(60),
            ShapePoint: _p.r(63),
            ShapeContainer: _p.r(61),
            Sweep: _p.r(68),
            Star: _p.r(65),
            Text: _p.r(69),
            TextSpan: _p.r(71),
            Use: _p.r(72),
            Vector: _p.r(73),
            g: _p.r(34),
            // animate
            Animator: _p.r(0),
            Easing: _p.r(1),
            OpacityAnimator: _p.r(4),
            RotateAnimator: _p.r(6),
            ScaleAnimator: _p.r(7),
            Timeline: _p.r(8),
            TranslateAnimator: _p.r(9),
            PathAnimator: _p.r(5),
            MotionAnimator: _p.r(3),
            requestFrame: _p.r(2).requestFrame,
            releaseFrame: _p.r(2).releaseFrame,
            // filter
            Filter: _p.r(20),
            GaussianblurFilter: _p.r(21),
            ProjectionFilter: _p.r(22),
            // effect
            ColorMatrixEffect: _p.r(13),
            CompositeEffect: _p.r(14),
            ConvolveMatrixEffect: _p.r(15),
            Effect: _p.r(16),
            GaussianblurEffect: _p.r(17),
            OffsetEffect: _p.r(18)
        });
        return window.kity = kity;
    }
};

var moduleMapping = {
    kity: 76
};

function use(name) {
    _p.r([ moduleMapping[name] ]);
}
/* global use, inc: true */

/**
 * 模块暴露
 */
use('kity');
})();
var KityMinder = window.KM = window.KityMinder = function() {
    var instanceMap = {},
        instanceId = 0,
        uuidMap = {};
    return {
        version: '1.3.5',
        uuid: function(name) {
            name = name || 'unknown';
            uuidMap[name] = uuidMap[name] || 0;
            ++uuidMap[name];
            return name + '_' + uuidMap[name];
        },
        createMinder: function(renderTarget, options) {
            options = options || {};
            options.renderTo = Utils.isString(renderTarget) ? document.getElementById(renderTarget) : renderTarget;
            var minder = new Minder(options);
            this.addMinder(options.renderTo, minder);
            return minder;
        },
        addMinder: function(target, minder) {
            var id;
            if (typeof(target) === 'string') {
                id = target;
            } else {
                id = target.id || ("KM_INSTANCE_" + instanceId++);
            }
            instanceMap[id] = minder;
        },
        getMinder: function(target, options) {
            var id;
            if (typeof(target) === 'string') {
                id = target;
            } else {
                id = target.id || ("KM_INSTANCE_" + instanceId++);
            }
            return instanceMap[id] || this.createMinder(target, options);
        },
        //挂接多语言
        LANG: {}
    };
}();
var utils = Utils = KityMinder.Utils = {
    extend: kity.Utils.extend.bind(kity.Utils),

    listen: function(element, type, handler) {
        var types = utils.isArray(type) ? type : utils.trim(type).split(/\s+/),
            k = types.length;
        if (k)
            while (k--) {
                type = types[k];
                if (element.addEventListener) {
                    element.addEventListener(type, handler, false);
                } else {
                    if (!handler._d) {
                        handler._d = {
                            els: []
                        };
                    }
                    var key = type + handler.toString(),
                        index = utils.indexOf(handler._d.els, element);
                    if (!handler._d[key] || index == -1) {
                        if (index == -1) {
                            handler._d.els.push(element);
                        }
                        if (!handler._d[key]) {
                            handler._d[key] = function(evt) {
                                return handler.call(evt.srcElement, evt || window.event);
                            };
                        }
                        element.attachEvent('on' + type, handler._d[key]);
                    }
                }
            }
        element = null;
    },
    trim: function(str) {
        return str.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, '');
    },
    each: function(obj, iterator, context) {
        if (obj == null) return;
        if (obj.length === +obj.length) {
            for (var i = 0, l = obj.length; i < l; i++) {
                if (iterator.call(context, i, obj[i], obj) === false)
                    return false;
            }
        } else {
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    if (iterator.call(context, key, obj[key], obj) === false)
                        return false;
                }
            }
        }
    },
    addCssRule: function(key, style, doc) {
        var head, node;
        if (style === undefined || style && style.nodeType && style.nodeType == 9) {
            //获取样式
            doc = style && style.nodeType && style.nodeType == 9 ? style : (doc || document);
            node = doc.getElementById(key);
            return node ? node.innerHTML : undefined;
        }
        doc = doc || document;
        node = doc.getElementById(key);

        //清除样式
        if (style === '') {
            if (node) {
                node.parentNode.removeChild(node);
                return true
            }
            return false;
        }

        //添加样式
        if (node) {
            node.innerHTML = style;
        } else {
            node = doc.createElement('style');
            node.id = key;
            node.innerHTML = style;
            doc.getElementsByTagName('head')[0].appendChild(node);
        }
    },
    keys: function(plain) {
        var keys = [];
        for (var key in plain) {
            if (plain.hasOwnProperty(key)) {
                keys.push(key);
            }
        }
        return keys;
    },
    proxy: function(fn, context) {
        return function() {
            return fn.apply(context, arguments);
        };
    },
    indexOf: function(array, item, start) {
        var index = -1;
        start = this.isNumber(start) ? start : 0;
        this.each(array, function(v, i) {
            if (i >= start && v === item) {
                index = i;
                return false;
            }
        });
        return index;
    },
    argsToArray: function(args, index) {
        return Array.prototype.slice.call(args, index || 0);
    },
    clonePlainObject: function(source, target) {
        var tmp;
        target = target || {};
        for (var i in source) {
            if (source.hasOwnProperty(i)) {
                tmp = source[i];
                if (utils.isObject(tmp) || utils.isArray(tmp)) {
                    target[i] = utils.isArray(tmp) ? [] : {};
                    utils.clonePlainObject(source[i], target[i])
                } else {
                    target[i] = tmp;
                }
            }
        }
        return target;
    },
    compareObject: function(source, target) {
        var tmp;
        if (this.isEmptyObject(source) !== this.isEmptyObject(target)) {
            return false
        }
        if (this.getObjectLength(source) != this.getObjectLength(target)) {
            return false;
        }
        for (var p in source) {
            if (source.hasOwnProperty(p)) {
                tmp = source[p];
                if (target[p] === undefined) {
                    return false;
                }
                if (this.isObject(tmp) || this.isArray(tmp)) {
                    if (this.isObject(target[p]) !== this.isObject(tmp)) {
                        return false;
                    }
                    if (this.isArray(tmp) !== this.isArray(target[p])) {
                        return false;
                    }
                    if (this.compareObject(tmp, target[p]) === false) {
                        return false
                    }
                } else {
                    if (tmp != target[p]) {
                        return false
                    }
                }
            }
        }
        return true;
    },
    getObjectLength: function(obj) {
        if (this.isArray(obj) || this.isString(obj)) return obj.length;
        var count = 0;
        for (var key in obj)
            if (obj.hasOwnProperty(key)) count++;
        return count;
    },
    isEmptyObject: function(obj) {
        if (obj == null) return true;
        if (this.isArray(obj) || this.isString(obj)) return obj.length === 0;
        for (var key in obj)
            if (obj.hasOwnProperty(key)) return false;
        return true;
    },
    loadFile: function() {
        var tmpList = [];

        function getItem(doc, obj) {
            try {
                for (var i = 0, ci; ci = tmpList[i++];) {
                    if (ci.doc === doc && ci.url == (obj.src || obj.href)) {
                        return ci;
                    }
                }
            } catch (e) {
                return null;
            }

        }

        return function(doc, obj, fn) {
            var item = getItem(doc, obj);
            if (item) {
                if (item.ready) {
                    fn && fn();
                } else {
                    item.funs.push(fn)
                }
                return;
            }
            tmpList.push({
                doc: doc,
                url: obj.src || obj.href,
                funs: [fn]
            });
            if (!doc.body) {
                var html = [];
                for (var p in obj) {
                    if (p == 'tag') continue;
                    html.push(p + '="' + obj[p] + '"')
                }
                doc.write('<' + obj.tag + ' ' + html.join(' ') + ' ></' + obj.tag + '>');
                return;
            }
            if (obj.id && doc.getElementById(obj.id)) {
                return;
            }
            var element = doc.createElement(obj.tag);
            delete obj.tag;
            for (var p in obj) {
                element.setAttribute(p, obj[p]);
            }
            element.onload = element.onreadystatechange = function() {
                if (!this.readyState || /loaded|complete/.test(this.readyState)) {
                    item = getItem(doc, obj);
                    if (item.funs.length > 0) {
                        item.ready = 1;
                        for (var fi; fi = item.funs.pop();) {
                            fi();
                        }
                    }
                    element.onload = element.onreadystatechange = null;
                }
            };
            //            element.onerror = function () {
            //                throw Error('The load ' + (obj.href || obj.src) + ' fails,check the url settings of file ')
            //            };
            doc.getElementsByTagName("head")[0].appendChild(element);
        }
    }(),
    clone: function(source, target) {
        var tmp;
        target = target || {};
        for (var i in source) {
            if (source.hasOwnProperty(i)) {
                tmp = source[i];
                if (typeof tmp == 'object') {
                    target[i] = utils.isArray(tmp) ? [] : {};
                    utils.clone(source[i], target[i])
                } else {
                    target[i] = tmp;
                }
            }
        }
        return target;
    },
    unhtml: function(str, reg) {
        return str ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp);)?/g, function(a, b) {
            if (b) {
                return a;
            } else {
                return {
                    '<': '&lt;',
                    '&': '&amp;',
                    '"': '&quot;',
                    '>': '&gt;',
                    "'": '&#39;'
                }[a]
            }
        }) : '';
    },
    cloneArr:function(arr){
        return [].concat(arr);
    },
    clearWhitespace:function(str){
        return str.replace(/[\u200b\t\r\n]/g, '');
    },
    getValueByIndex:function(data,index){

        var initIndex = 0,result = 0;

        utils.each(data,function(i,arr){
            if(initIndex + arr.length >= index){

                if(index - initIndex == arr.length){
                    if(arr.length == 1 && arr[0].width === 0){
                        initIndex++;
                        return;
                    }
                    result = {
                        x: arr[arr.length - 1].x + arr[arr.length - 1].width,
                        y: arr[arr.length - 1].y
                    };
                }else{
                    result = arr[index - initIndex];
                }

                return false;
            }else{
                initIndex += arr.length + (arr.length == 1 && arr[0].width === 0 ? 0 : 1);
            }
        });
        return result;
    },
    getNodeIndex:function (node, ignoreTextNode) {
        var preNode = node,
            i = 0;
        while (preNode = preNode.previousSibling) {
            if (ignoreTextNode && preNode.nodeType == 3) {
                if(preNode.nodeType != preNode.nextSibling.nodeType ){
                    i++;
                }
                continue;
            }
            i++;
        }
        return i;
    }


};

Utils.each(['String', 'Function', 'Array', 'Number', 'RegExp', 'Object'], function(i, v) {
    KityMinder.Utils['is' + v] = function(obj) {
        return Object.prototype.toString.apply(obj) == '[object ' + v + ']';
    }
});
/**
 * 提供浏览器检测的模块
 * @unfile
 * @module KM.browser
 */
var browser = KityMinder.browser = function(){
    var agent = navigator.userAgent.toLowerCase(),
        opera = window.opera,
        browser = {
        /**
         * @property {boolean} ie 检测当前浏览器是否为IE
         * @example
         * ```javascript
         * if ( UE.browser.ie ) {
         *     console.log( '当前浏览器是IE' );
         * }
         * ```
         */
        ie		:  /(msie\s|trident.*rv:)([\w.]+)/.test(agent),

        /**
         * @property {boolean} opera 检测当前浏览器是否为Opera
         * @example
         * ```javascript
         * if ( UE.browser.opera ) {
         *     console.log( '当前浏览器是Opera' );
         * }
         * ```
         */
        opera	: ( !!opera && opera.version ),

        /**
         * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器
         * @example
         * ```javascript
         * if ( UE.browser.webkit ) {
         *     console.log( '当前浏览器是webkit内核浏览器' );
         * }
         * ```
         */
        webkit	: ( agent.indexOf( ' applewebkit/' ) > -1 ),

        /**
         * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下
         * @example
         * ```javascript
         * if ( UE.browser.mac ) {
         *     console.log( '当前浏览器运行在mac平台下' );
         * }
         * ```
         */
        mac	: ( agent.indexOf( 'macintosh' ) > -1 ),

        /**
         * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下
         * @example
         * ```javascript
         * if ( UE.browser.quirks ) {
         *     console.log( '当前浏览器运行处于“怪异模式”' );
         * }
         * ```
         */
        quirks : ( document.compatMode == 'BackCompat' ),

        ipad :  ( agent.indexOf( 'ipad' ) > -1 )
    };

    /**
    * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核
    * @example
    * ```javascript
    * if ( UE.browser.gecko ) {
    *     console.log( '当前浏览器内核是gecko内核' );
    * }
    * ```
    */
    browser.gecko =( navigator.product == 'Gecko' && !browser.webkit && !browser.opera && !browser.ie);

    var version = 0;

    // Internet Explorer 6.0+
    if ( browser.ie ){

        var v1 =  agent.match(/(?:msie\s([\w.]+))/);
        var v2 = agent.match(/(?:trident.*rv:([\w.]+))/);
        if(v1 && v2 && v1[1] && v2[1]){
            version = Math.max(v1[1]*1,v2[1]*1);
        }else if(v1 && v1[1]){
            version = v1[1]*1;
        }else if(v2 && v2[1]){
            version = v2[1]*1;
        }else{
            version = 0;
        }

        browser.ie11Compat = document.documentMode == 11;
        /**
         * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie9Compat ) {
         *     console.log( '当前浏览器运行在IE9兼容模式下' );
         * }
         * ```
         */
        browser.ie9Compat = document.documentMode == 9;

        /**
         * @property { boolean } ie8 检测浏览器是否是IE8浏览器
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie8 ) {
         *     console.log( '当前浏览器是IE8浏览器' );
         * }
         * ```
         */
        browser.ie8 = !!document.documentMode;

        /**
         * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie8Compat ) {
         *     console.log( '当前浏览器运行在IE8兼容模式下' );
         * }
         * ```
         */
        browser.ie8Compat = document.documentMode == 8;

        /**
         * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie7Compat ) {
         *     console.log( '当前浏览器运行在IE7兼容模式下' );
         * }
         * ```
         */
        browser.ie7Compat = ( ( version == 7 && !document.documentMode )
                || document.documentMode == 7 );

        /**
         * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie6Compat ) {
         *     console.log( '当前浏览器运行在IE6模式或者怪异模式下' );
         * }
         * ```
         */
        browser.ie6Compat = ( version < 7 || browser.quirks );

        browser.ie9above = version > 8;

        browser.ie9below = version < 9;

    }

    // Gecko.
    if ( browser.gecko ){
        var geckoRelease = agent.match( /rv:([\d\.]+)/ );
        if ( geckoRelease )
        {
            geckoRelease = geckoRelease[1].split( '.' );
            version = geckoRelease[0] * 10000 + ( geckoRelease[1] || 0 ) * 100 + ( geckoRelease[2] || 0 ) * 1;
        }
    }

    /**
     * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是，则返回Chrome的大版本号
     * @warning 如果浏览器不是chrome， 则该值为undefined
     * @example
     * ```javascript
     * if ( UE.browser.chrome ) {
     *     console.log( '当前浏览器是Chrome' );
     * }
     * ```
     */
    if (/chrome\/(\d+\.\d)/i.test(agent)) {
        browser.chrome = + RegExp['\x241'];
    }

    /**
     * @property { Number } safari 检测当前浏览器是否为Safari, 如果是，则返回Safari的大版本号
     * @warning 如果浏览器不是safari， 则该值为undefined
     * @example
     * ```javascript
     * if ( UE.browser.safari ) {
     *     console.log( '当前浏览器是Safari' );
     * }
     * ```
     */
    if(/(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) && !/chrome/i.test(agent)){
    	browser.safari = + (RegExp['\x241'] || RegExp['\x242']);
    }


    // Opera 9.50+
    if ( browser.opera )
        version = parseFloat( opera.version() );

    // WebKit 522+ (Safari 3+)
    if ( browser.webkit )
        version = parseFloat( agent.match( / applewebkit\/(\d+)/ )[1] );

    /**
     * @property { Number } version 检测当前浏览器版本号
     * @remind
     * <ul>
     *     <li>IE系列返回值为5,6,7,8,9,10等</li>
     *     <li>gecko系列会返回10900，158900等</li>
     *     <li>webkit系列会返回其build号 (如 522等)</li>
     * </ul>
     * @example
     * ```javascript
     * console.log( '当前浏览器版本号是： ' + UE.browser.version );
     * ```
     */
    browser.version = version;

    /**
     * @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容
     * @example
     * ```javascript
     * if ( UE.browser.isCompatible ) {
     *     console.log( '浏览器与UEditor能够良好兼容' );
     * }
     * ```
     */
    browser.isCompatible =
        !browser.mobile && (
        ( browser.ie && version >= 6 ) ||
        ( browser.gecko && version >= 10801 ) ||
        ( browser.opera && version >= 9.5 ) ||
        ( browser.air && version >= 1 ) ||
        ( browser.webkit && version >= 522 ) ||
        false );
    return browser;
}();
//快捷方式
var ie = browser.ie,
    webkit = browser.webkit,
    gecko = browser.gecko,
    opera = browser.opera;
/* jshint -W079 */
var Minder = KityMinder.Minder = kity.createClass('KityMinder', {
    constructor: function(options) {
        this._options = Utils.extend(window.KITYMINDER_CONFIG || {}, options);

        var initQueue = Minder._initFnQueue.slice();

        // @see option.js
        // @see event.js
        // @see status.js
        // @see paper.js
        // @see select.js
        // @see key.js
        // @see contextmenu.js
        // @see module.js
        // @see data.js         
        // @see readonly.js
        // @see layout.js
        // @see theme.js
        while (initQueue.length) initQueue.shift().call(this, options);
        
        this.fire('ready');
    }
});
/* jshint +W079 */

Minder._initFnQueue = [];
Minder.registerInit = function(fn) {
    Minder._initFnQueue.push(fn);
};
var Command = kity.createClass( "Command", {
	constructor: function () {
		this._isContentChange = true;
		this._isSelectionChange = false;
	},

	execute: function ( minder, args ) {

	},

	setContentChanged: function ( val ) {
		this._isContentChange = !! val;
	},

	isContentChanged: function () {
		return this._isContentChange;
	},

	setSelectionChanged: function ( val ) {
		this._isSelectionChange = !! val;
	},

	isSelectionChanged: function () {
		return this._isContentChange;
	},

	queryState: function ( km ) {
		return 0;
	},

	queryValue: function ( km ) {
		return 0;
	},
	isNeedUndo: function () {
		return true;
	}
} );

kity.extendClass(Minder, {
    _getCommand: function (name) {
        return this._commands[name.toLowerCase()];
    },

    _queryCommand: function (name, type, args) {
        var cmd = this._getCommand(name);
        if (cmd) {
            var queryCmd = cmd['query' + type];
            if (queryCmd)
                return queryCmd.apply(cmd, [this].concat(args));
        }
        return 0;
    },

    queryCommandState: function (name) {
        return this._queryCommand(name, "State", Utils.argsToArray(1));
    },

    queryCommandValue: function (name) {
        return this._queryCommand(name, "Value", Utils.argsToArray(1));
    },

    execCommand: function (name) {
        name = name.toLowerCase();

        var cmdArgs = Utils.argsToArray(arguments, 1),
            cmd, stoped, result, eventParams;
        var me = this;
        cmd = this._getCommand(name);

        eventParams = {
            command: cmd,
            commandName: name.toLowerCase(),
            commandArgs: cmdArgs
        };
        if (!cmd || !~this.queryCommandState(name)) {
            return false;
        }

        if (!this._hasEnterExecCommand && cmd.isNeedUndo()) {
            this._hasEnterExecCommand = true;
            stoped = this._fire(new MinderEvent('beforeExecCommand', eventParams, true));

            if (!stoped) {
                //保存场景
                this._fire(new MinderEvent('saveScene'));

                this._fire(new MinderEvent("preExecCommand", eventParams, false));

                result = cmd.execute.apply(cmd, [me].concat(cmdArgs));

                this._fire(new MinderEvent('execCommand', eventParams, false));

                //保存场景
                this._fire(new MinderEvent('saveScene'));

                if (cmd.isContentChanged()) {
                    this._firePharse(new MinderEvent('contentchange'));
                }

                this._interactChange();
            }
            this._hasEnterExecCommand = false;
        } else {
            result = cmd.execute.apply(cmd, [me].concat(cmdArgs));

            if (!this._hasEnterExecCommand) {
                this._interactChange();
            }
        }

        return result === undefined ? null : result;
    }
});
/* jshint -W079 */
var MinderNode = KityMinder.MinderNode = kity.createClass('MinderNode', {
/* jshint +W079 */

    /**
     * 创建一个节点
     *
     * @param {KityMinder}    minder
     *     节点绑定的脑图的实例
     *
     * @param {String|Object} unknown
     *     节点的初始数据或文本
     */
    constructor: function(unknown) {
        this.parent = null;
        this.root = this;
        this.children = [];
        this.data = {};
        this.tmpData = {};

        this.initContainers();

        if (Utils.isString(unknown)) {
            this.setText(unknown);
        } else {
            this.setData(unknown);
        }
    },

    initContainers: function() {
        this.rc = new kity.Group().setId(KityMinder.uuid('minder_node'));
        this.rc.minderNode = this;
    },

    /**
     * 判断节点是否根节点
     */
    isRoot: function() {
        return this.root === this;
    },

    /**
     * 判断节点是否叶子
     */
    isLeaf: function() {
        return this.children.length === 0;
    },

    /**
     * 获取节点的根节点
     */
    getRoot: function() {
        return this.root || this;
    },

    /**
     * 获得节点的父节点
     */
    getParent: function() {
        return this.parent;
    },

    /**
     * 获得节点的深度
     */
    getLevel: function() {
        var level = 0,
            parent = this.parent;
        while (parent) {
            level++;
            parent = parent.parent;
        }
        return level;
    },

    /**
     * 获得节点的复杂度（即子树中节点的数量）
     */
    getComplex: function() {
        var complex = 0;
        this.traverse(function() {
            complex++;
        });
        return complex;
    },

    /**
     * 获得节点的类型（root|main|sub）
     */
    getType: function(type) {
        this.type = ['root', 'main', 'sub'][Math.min(this.getLevel(), 2)];
        return this.type;
    },

    /**
     * 判断当前节点是否被测试节点的祖先
     * @param  {MinderNode}  test 被测试的节点
     */
    isAncestorOf: function(test) {
        var p = test.parent;
        while (p) {
            if (p == this) return true;
            p = p.parent;
        }
        return false;
    },

    /**
     * 设置节点的文本数据
     * @param {String} text 文本数据
     */
    setText: function(text) {
        if(utils.isArray(text)){
            text = text.join('\n');
        }
        return this.setData('text', text);
    },

    /**
     * 获取节点的文本数据
     * @return {String}
     */
    getText: function(str2arr) {
        var text = this.getData('text') || '';

        if(str2arr){
            text = text.split('\n');
        }

        return text;
    },

    /**
     * 先序遍历当前节点树
     * @param  {Function} fn 遍历函数
     */
    preTraverse: function(fn, excludeThis) {
        var children = this.getChildren();
        if (!excludeThis) fn(this);
        for (var i = 0; i < children.length; i++) {
            children[i].preTraverse(fn);
        }
    },

    /**
     * 后序遍历当前节点树
     * @param  {Function} fn 遍历函数
     */
    postTraverse: function(fn, excludeThis) {
        var children = this.getChildren();
        for (var i = 0; i < children.length; i++) {
            children[i].postTraverse(fn);
        }
        if (!excludeThis) fn(this);
    },

    traverse: function(fn, excludeThis) {
        return this.postTraverse(fn, excludeThis);
    },

    getChildren: function() {
        return this.children;
    },

    getIndex: function() {
        return this.parent ? this.parent.children.indexOf(this) : -1;
    },

    insertChild: function(node, index) {
        if (index === undefined) {
            index = this.children.length;
        }
        if (node.parent) {
            node.parent.removeChild(node);
        }
        node.parent = this;
        node.root = this.root;

        this.children.splice(index, 0, node);
    },

    appendChild: function(node) {
        return this.insertChild(node);
    },

    prependChild: function(node) {
        return this.insertChild(node, 0);
    },

    removeChild: function(elem) {
        var index = elem,
            removed;
        if (elem instanceof MinderNode) {
            index = this.children.indexOf(elem);
        }
        if (index >= 0) {
            removed = this.children.splice(index, 1)[0];
            removed.parent = null;
            removed.root = removed;
        }
    },

    getChild: function(index) {
        return this.children[index];
    },

    getFirstChild: function() {
        return this.children[0];
    },

    getLastChild: function() {
        return this.children[this.children.length - 1];
    },

    getData: function(name) {
        if (name === undefined) {
            return this.data;
        }
        return this.data[name];
    },

    setData: function(name, value) {
        if (name === undefined) {
            this.data = {};

        } else if (utils.isObject(name)) {
            Utils.extend(this.data, name);
        } else {
            if (value === undefined) {
                this.data[name] = null;
                delete this.data[name];
            } else {
                this.data[name] = value;
            }
        }
        return this;
    },

    getRenderContainer: function() {
        return this.rc;
    },

    getCommonAncestor: function(node) {
        return Utils.getNodeCommonAncestor(this, node);
    },

    contains: function(node) {
        return this == node || this.isAncestorOf(node);
    },

    clone: function() {
        function cloneNode(parent, isClonedNode) {
            var cloned = new KM.MinderNode();

            cloned.data = Utils.clonePlainObject(isClonedNode.getData());
            cloned.tmpData = Utils.clonePlainObject(isClonedNode.getTmpData());

            if (parent) {
                parent.appendChild(cloned);
            }
            for (var i = 0, ci;
                (ci = isClonedNode.children[i++]);) {
                cloneNode(cloned, ci);
            }
            return cloned;
        }
        return cloneNode(null, this);
    },

    equals: function(node,ignoreSelected) {
        var me = this;
        function restoreSelected(){
            if(isSelectedA){
                me.setSelectedFlag();
            }
            if(isSelectedB){
                node.setSelectedFlag();
            }
        }
        if(ignoreSelected){
            var isSelectedA = false;
            var isSelectedB = false;
            if(me.isSelected()){
                isSelectedA = true;
                me.clearSelectedFlag();
            }

            if(node.isSelected()){
                isSelectedB = true;
                node.clearSelectedFlag();
            }
        }
        if (node.children.length != this.children.length) {
            restoreSelected();
            return false;
        }
        if (utils.compareObject(node.getData(), me.getData()) === false) {
            restoreSelected();
            return false;
        }
        if (utils.compareObject(node.getTmpData(), me.getTmpData()) === false) {
            restoreSelected();
            return false;
        }
        for (var i = 0, ci;
            (ci = me.children[i]); i++) {
            if (ci.equals(node.children[i],ignoreSelected) === false) {
                restoreSelected();
                return false;
            }
        }
        restoreSelected();
        return true;

    },

    clearChildren: function() {
        this.children = [];
    },

    setTmpData: function(a, v) {
        var me = this;
        if (utils.isObject(a)) {
            utils.each(a, function(key, val) {
                me.setTmpData(key, val);
            });
        }
        if (v === undefined || v === null || v === '') {
            delete this.tmpData[a];
        } else {
            this.tmpData[a] = v;
        }
    },

    getTmpData: function(a) {
        if (a === undefined) {
            return this.tmpData;
        }
        return this.tmpData[a];
    },

    setValue: function(node) {
        this.data = {};
        this.setData(utils.clonePlainObject(node.getData()));
        this.tmpData = {};
        this.setTmpData(utils.clonePlainObject(node.getTmpData()));
        return this;
    }
});

MinderNode.getCommonAncestor = function(nodeA, nodeB) {
    if (nodeA instanceof Array) {
        return MinderNode.getCommonAncestor.apply(this, nodeA);
    }
    switch (arguments.length) {
        case 1:
            return nodeA.parent || nodeA;

        case 2:
            if (nodeA.isAncestorOf(nodeB)) {
                return nodeA;
            }
            if (nodeB.isAncestorOf(nodeA)) {
                return nodeB;
            }
            var ancestor = nodeA.parent;
            while (ancestor && !ancestor.isAncestorOf(nodeB)) {
                ancestor = ancestor.parent;
            }
            return ancestor;

        default:
            return Array.prototype.reduce.call(arguments, function(prev, current) {
                return MinderNode.getCommonAncestor(prev, current);
            }, nodeA);
    }
};

kity.extendClass(Minder, {

    getRoot: function() {
        return this._root;
    },

    setRoot: function(root) {
        this._root = root;
        root.minder = this;
    },

    createNode: function(unknown, parent, index) {
        var node = new MinderNode(unknown);
        this.fire('nodecreate', { node: node, parent: parent, index: index });
        this.appendNode(node, parent, index);
        return node;
    },

    appendNode: function(node, parent, index) {
        if (parent) parent.insertChild(node, index);
        this.attachNode(node);
        return this;
    },

    removeNode: function(node) {
        if (node.parent) {
            node.parent.removeChild(node);
            this.detachNode(node);
            this.fire('noderemove', { node: node });
        }
    },

    attachNode: function(node) {
        var rc = this._rc;
        node.traverse(function(current) {
            current.attached = true;
            rc.addShape(current.getRenderContainer());
        });
        rc.addShape(node.getRenderContainer());
        this.fire('nodeattach', {
            node: node
        });
    },

    detachNode: function(node) {
        var rc = this._rc;
        node.traverse(function(current) {
            current.attached = false;
            rc.removeShape(current.getRenderContainer());
        });
        this.fire('nodedetach', {
            node: node
        });
    },

    getMinderTitle: function() {
        return this.getRoot().getText();
    }

});

kity.extendClass(MinderNode, {
    getMinder: function() {
        return this.getRoot().minder;
    }
});

/**
 * @fileOverview
 *
 * 提供脑图选项支持
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
kity.extendClass(Minder, {

    getOptions: function(key) {
        var val;
        if (key) {
            val = this.getPreferences(key);
            return val && val[key] || this._options[key];
        } else {
            val = this.getPreferences();
            return utils.extend(val, this._options, true);
        }
    },

    setDefaultOptions: function(key, val, cover) {
        var obj = {};
        if (Utils.isString(key)) {
            obj[key] = val;
        } else {
            obj = key;
        }
        utils.extend(this._options, obj, !cover);
    },

    setOptions: function(key, val) {
        this.setPreferences(key, val);
    }

});

Minder.registerInit(function(option) {
    this.setDefaultOptions(KM.defaultOptions);
});
var MinderEvent = kity.createClass('MindEvent', {
    constructor: function(type, params, canstop) {
        params = params || {};
        if (params.getType && params.getType() == 'ShapeEvent') {
            this.kityEvent = params;
            this.originEvent = params.originEvent;
            this.getPosition = params.getPosition.bind(params);
        } else if (params.target && params.preventDefault) {
            this.originEvent = params;
        } else {
            kity.Utils.extend(this, params);
        }
        this.type = type;
        this._canstop = canstop || false;
    },

    getTargetNode: function() {
        var findShape = this.kityEvent && this.kityEvent.targetShape;
        if (!findShape) return null;
        while (!findShape.minderNode && findShape.container) {
            findShape = findShape.container;
        }
        var node = findShape.minderNode;
        if (node && findShape.getOpacity() < 1) return null;
        return node || null;
    },

    stopPropagation: function() {
        this._stoped = true;
    },

    stopPropagationImmediately: function() {
        this._immediatelyStoped = true;
        this._stoped = true;
    },

    shouldStopPropagation: function() {
        return this._canstop && this._stoped;
    },

    shouldStopPropagationImmediately: function() {
        return this._canstop && this._immediatelyStoped;
    },
    preventDefault: function() {
        this.originEvent.preventDefault();
    },
    isRightMB: function() {
        var isRightMB = false;
        if (!this.originEvent) {
            return false;
        }
        if ("which" in this.originEvent)
            isRightMB = this.originEvent.which == 3;
        else if ("button" in this.originEvent)
            isRightMB = this.originEvent.button == 2;
        return isRightMB;
    },
    getKeyCode: function(){
        var evt = this.originEvent;
        return evt.keyCode || evt.which;
    }
});

Minder.registerInit(function() {
    this._initEvents();
});

// 事件机制
kity.extendClass(Minder, {
    _initEvents: function() {
        this._eventCallbacks = {};
    },
    _bindEvents: function() {
        this._bindPaperEvents();
        this._bindKeyboardEvents();
    },
    _resetEvents: function() {
        this._initEvents();
        this._bindEvents();
    },
    // TODO: mousemove lazy bind
    _bindPaperEvents: function() {
        this._paper.on('click dblclick mousedown contextmenu mouseup mousemove mouseover mousewheel DOMMouseScroll touchstart touchmove touchend dragenter dragleave drop', this._firePharse.bind(this));
        if (window) {
            window.addEventListener('resize', this._firePharse.bind(this));
            window.addEventListener('blur', this._firePharse.bind(this));
        }
    },
    _bindKeyboardEvents: function() {
        if ((navigator.userAgent.indexOf('iPhone') == -1) && (navigator.userAgent.indexOf('iPod') == -1) && (navigator.userAgent.indexOf('iPad') == -1)) {
            //只能在这里做，要不无法触发
            Utils.listen(document.body, 'keydown keyup keypress paste', this._firePharse.bind(this));
        }
    },
    _firePharse: function(e) {
        //        //只读模式下强了所有的事件操作
        //        if(this.readOnly === true){
        //            return false;
        //        }
        var beforeEvent, preEvent, executeEvent;

        if (e.type == 'DOMMouseScroll') {
            e.type = 'mousewheel';
            e.wheelDelta = e.originEvent.wheelDelta = e.originEvent.detail * -10;
            e.wheelDeltaX = e.originEvent.mozMovementX;
            e.wheelDeltaY = e.originEvent.mozMovementY;
        }

        beforeEvent = new MinderEvent('before' + e.type, e, true);
        if (this._fire(beforeEvent)) {
            return;
        }
        preEvent = new MinderEvent('pre' + e.type, e, true);
        executeEvent = new MinderEvent(e.type, e, true);

        if (this._fire(preEvent) ||
            this._fire(executeEvent))
            this._fire(new MinderEvent('after' + e.type, e, false));
    },
    _interactChange: function(e) {
        var me = this;
        if (me._interactScheduled) return;
        setTimeout(function() {
            me._fire(new MinderEvent('interactchange'));
            me._interactScheduled = false;
        }, 100);
        me._interactScheduled = true;
    },
    _listen: function(type, callback) {
        var callbacks = this._eventCallbacks[type] || (this._eventCallbacks[type] = []);
        callbacks.push(callback);
    },
    _fire: function(e) {


        var status = this.getStatus();

        var callbacks = this._eventCallbacks[e.type.toLowerCase()] || [];

        if (status) {

            callbacks = callbacks.concat(this._eventCallbacks[status + '.' + e.type.toLowerCase()] || []);
        }



        if (callbacks.length === 0) {
            return;
        }
        var lastStatus = this.getStatus();

        for (var i = 0; i < callbacks.length; i++) {

            callbacks[i].call(this, e);

            /* this.getStatus() != lastStatus ||*/
            if (e.shouldStopPropagationImmediately()) {
                break;
            }
        }
        return e.shouldStopPropagation();
    },
    on: function(name, callback) {
        var km = this;
        utils.each(name.split(/\s+/), function(i, n) {
            km._listen(n.toLowerCase(), callback);
        });
        return this;
    },
    off: function(name, callback) {

        var types = name.split(/\s+/);
        var i, j, callbacks, removeIndex;
        for (i = 0; i < types.length; i++) {

            callbacks = this._eventCallbacks[types[i].toLowerCase()];
            if (callbacks) {
                removeIndex = null;
                for (j = 0; j < callbacks.length; j++) {
                    if (callbacks[j] == callback) {
                        removeIndex = j;
                    }
                }
                if (removeIndex !== null) {
                    callbacks.splice(removeIndex, 1);
                }
            }
        }
    },
    fire: function(type, params) {
        var e = new MinderEvent(type, params);
        this._fire(e);
        return this;
    }
});
/**
 * @fileOverview
 *
 * 状态切换控制
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

Minder.registerInit(function() {
    this._initStatus();
});

kity.extendClass(Minder, {

    _initStatus: function() {
        this._status = 'normal';
        this._rollbackStatus = 'normal';
    },

    setStatus: (function() {
        var sf = ~window.location.href.indexOf('status');
        var tf = ~window.location.href.indexOf('trace');

        // 在 readonly 模式下，只有 force 为 true 才能切换回来
        return function(status, force) {
            if (this._status == 'readonly' && !force) return this;
            if (status != this._status) {
                this._rollbackStatus = this._status;
                this._status = status;
                this.fire('statuschange', {
                    lastStatus: this._rollbackStatus,
                    currentStatus: this._status
                });
                if (sf) {
                    console.log(window.event.type, this._rollbackStatus, '->', this._status);
                    if (tf) {
                        console.trace();
                    }
                }
            }
            return this;
        };
    })(),

    rollbackStatus: function() {
        this.setStatus(this._rollbackStatus);
    },
    getRollbackStatus:function(){
        return this._rollbackStatus;
    },
    getStatus: function() {
        return this._status;
    }
});
/**
 * @fileOverview
 *
 * 初始化渲染容器
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
Minder.registerInit(function() {
    this._initPaper();
});
kity.extendClass(Minder, {
    
    _initPaper: function() {

        this._paper = new kity.Paper();
        this._paper.getNode().setAttribute('contenteditable', true);
        this._paper.getNode().ondragstart = function(e) {
            e.preventDefault();
        };
        this._paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)');

        this._addRenderContainer();

        this.setRoot(this.createNode(this.getLang().maintopic));

        if (this._options.renderTo) {
            this.renderTo(this._options.renderTo);
        }
    },

    _addRenderContainer: function() {
        this._rc = new kity.Group().setId(KityMinder.uuid('minder'));
        this._paper.addShape(this._rc);
    },

    renderTo: function(target) {
        this._paper.renderTo(this._renderTarget = target);
        this._bindEvents();
    },

    getRenderContainer: function() {
        return this._rc;
    },

    getPaper: function() {
        return this._paper;
    },

    getRenderTarget: function() {
        return this._renderTarget;
    },
});

Minder.registerInit(function() {
    this._initSelection();
});

// 选区管理
kity.extendClass(Minder, {
    _initSelection: function() {
        this._selectedNodes = [];
    },
    renderChangedSelection: function(last) {
        var current = this.getSelectedNodes();
        var changed = [];
        var i = 0;

        current.forEach(function(node) {
            if (last.indexOf(node) == -1) {
                changed.push(node);
                node.setTmpData('selected', true);
            }
        });

        last.forEach(function(node) {
            if (current.indexOf(node) == -1) {
                changed.push(node);
                node.setTmpData('selected', false);
            }
        });

        if (changed.length) {
            this._interactChange();
            this.fire('selectionchange');
        }
        while (i < changed.length) changed[i++].render();
    },
    getSelectedNodes: function() {
        //不能克隆返回，会对当前选区操作，从而影响querycommand
        return this._selectedNodes;
    },
    getSelectedNode: function() {
        return this.getSelectedNodes()[0] || null;
    },
    removeAllSelectedNodes: function() {
        var me = this;
        var last = this._selectedNodes.splice(0);
        this._selectedNodes = [];
        this.renderChangedSelection(last);
        return this.fire('selectionclear');
    },
    removeSelectedNodes: function(nodes) {
        var me = this;
        var last = this._selectedNodes.slice(0);
        nodes = Utils.isArray(nodes) ? nodes : [nodes];
        Utils.each(nodes, function(i, n) {
            var index;
            if ((index = me._selectedNodes.indexOf(n)) === -1) return;
            me._selectedNodes.splice(index, 1);
        });
        this.renderChangedSelection(last);
        return this;
    },
    select: function(nodes, isSingleSelect) {
        var lastSelect = this.getSelectedNodes().slice(0);
        if (isSingleSelect) {
            this._selectedNodes = [];
        }
        var me = this;
        nodes = Utils.isArray(nodes) ? nodes : [nodes];
        Utils.each(nodes, function(i, n) {
            if (me._selectedNodes.indexOf(n) !== -1) return;
            me._selectedNodes.push(n);
        });
        this.renderChangedSelection(lastSelect);
        return this;
    },

    //当前选区中的节点在给定的节点范围内的保留选中状态，
    //没在给定范围的取消选中，给定范围中的但没在当前选中范围的也做选中效果
    toggleSelect: function(node) {
        if (Utils.isArray(node)) {
            node.forEach(this.toggleSelect.bind(this));
        } else {
            if (node.isSelected()) this.removeSelectedNodes(node);
            else this.select(node);
        }
        return this;
    },

    isSingleSelect: function() {
        return this._selectedNodes.length == 1;
    },

    getSelectedAncestors: function(includeRoot) {
        var nodes = this.getSelectedNodes().slice(0),
            ancestors = [],
            judge;

        // 根节点不参与计算
        var rootIndex = nodes.indexOf(this.getRoot());
        if (~rootIndex && !includeRoot) {
            nodes.splice(rootIndex, 1);
        }

        // 判断 nodes 列表中是否存在 judge 的祖先
        function hasAncestor(nodes, judge) {
            for (var i = nodes.length - 1; i >= 0; --i) {
                if (nodes[i].isAncestorOf(judge)) return true;
            }
            return false;
        }

        // 按照拓扑排序
        nodes.sort(function(node1, node2) {
            return node1.getLevel() - node2.getLevel();
        });

        // 因为是拓扑有序的，所以只需往上查找
        while ((judge = nodes.pop())) {
            if (!hasAncestor(nodes, judge)) {
                ancestors.push(judge);
            }
        }

        return ancestors;
    }
});

kity.extendClass(MinderNode, {
    isSelected: function() {
        return this.getTmpData('selected');
    },
    clearSelectedFlag:function(){
        this.setTmpData('selected');
    },
    setSelectedFlag:function(){
        this.setTmpData('selected',true);
    }
});
/**
 * @fileOverview
 *
 * 添加快捷键支持
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

/**
 * 计算包含 meta 键的 keycode
 *
 * @param  {String|KeyEvent} unknown
 */
function getMetaKeyCode(unknown) {
    var CTRL_MASK = 0x1000;
    var ALT_MASK = 0x2000;
    var SHIFT_MASK = 0x4000;
    var metaKeyCode = 0;

    if (typeof(unknown) == 'string') {
        // unknown as string
        unknown.toLowerCase().split(/\+\s*/).forEach(function(name) {
            switch(name) {
                case 'ctrl':
                case 'cmd':
                    metaKeyCode |= CTRL_MASK;
                    break;
                case 'alt':
                    metaKeyCode |= ALT_MASK;
                    break;
                case 'shift':
                    metaKeyCode |= SHIFT_MASK;
                    break;
                default:
                    metaKeyCode |= keymap[name];
            }
        });
    } else {
        // unknown as key event
        if (unknown.ctrlKey || unknown.metaKey) {
            metaKeyCode |= CTRL_MASK;
        }
        if (unknown.altKey) {
            metaKeyCode |= ALT_MASK;
        }
        if (unknown.shiftKey) {
            metaKeyCode |= SHIFT_MASK;
        }
        metaKeyCode |= unknown.keyCode;
    }

    return metaKeyCode;
}
kity.extendClass(MinderEvent, {
    isShortcutKey: function(keyCombine) {
        var keyEvent = this.originEvent;
        if (!keyEvent) return false;

        return getMetaKeyCode(keyCombine) == getMetaKeyCode(keyEvent);
    }
});

Minder.registerInit(function() {
    this._initShortcutKey();
});

kity.extendClass(Minder, {

    _initShortcutKey: function() {
        this._bindShortcutKeys();
    },
    
    _bindShortcutKeys: function() {
        var map = this._shortcutKeys = {};
        var has = 'hasOwnProperty';
        this.on('keydown', function(e) {
            for (var keys in map) {
                if (!map[has](keys)) continue;
                if (e.isShortcutKey(keys)) {
                    var fn = map[keys];
                    if (fn.__statusCondition && fn.__statusCondition != this.getStatus()) return;
                    fn();
                    e.preventDefault();
                }
            }
        });
    },
    
    addShortcut: function(keys, fn) {
        var binds = this._shortcutKeys;
        keys.split(/\|\s*/).forEach(function(combine) {
            var parts = combine.split('::');
            var status;
            if (parts.length > 1) {
                combine = parts[1];
                status = parts[0];
                fn.__statusCondition = status;
            }
            binds[combine] = fn;
        });
    },

    addCommandShortcutKeys: function(cmd, keys) {
        var binds = this._commandShortcutKeys || (this._commandShortcutKeys = {});
        var obj = {},
            km = this;
        if (keys) {
            obj[cmd] = keys;
        } else {
            obj = cmd;
        }

        var minder = this;

        utils.each(obj, function(command, keys) {

            binds[command] = keys;

            minder.addShortcut(keys, function execCommandByShortcut() {
                if (minder.queryCommandState(command) === 0) {
                    minder.execCommand(command);
                }
            });
        });
    },

    getCommandShortcutKey: function(cmd) {
        var binds = this._commandShortcutKeys;
        return binds && binds[cmd] || null;
    }
});
/**
 * @fileOverview
 *
 * 添加模块上下文菜单支持
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

Minder.registerInit(function() {
    this._initContextMenu();
});

kity.extendClass(Minder, {
    _initContextMenu: function() {
        this.contextmenus = [];
    },
    addContextMenu: function(item) {
        if (utils.isArray(item)) {
            this.contextmenus = this.contextmenus.concat(item);
        } else {
            this.contextmenus.push(item);
        }

        return this;
    },
    getContextMenu: function() {
        return this.contextmenus;
    }
});
//模块注册&暴露模块接口
( function () {
    var _modules;
    KityMinder.registerModule = function ( name, module ) {
        //初始化模块列表
        if ( !_modules ) {
            _modules = {};
        }
        _modules[ name ] = module;
    };
    KityMinder.getModules = function () {
        return _modules;
    };
} )();

Minder.registerInit(function() {
    this._initModules();
});

// 模块声明周期维护
kity.extendClass(Minder, {
    _initModules: function() {
        var modulesPool = KityMinder.getModules();
        var modulesToLoad = this._options.modules || Utils.keys(modulesPool);

        this._commands = {};
        this._query = {};
        this._modules = {};
        this._rendererClasses = {};

        var i, name, type, module, moduleDeals,
            dealCommands, dealEvents, dealRenderers;

        var me = this;
        for (i = 0; i < modulesToLoad.length; i++) {
            name = modulesToLoad[i];

            if (!modulesPool[name]) continue;

            // 执行模块初始化，抛出后续处理对象

            if (typeof(modulesPool[name]) == 'function') {
                moduleDeals = modulesPool[name].call(me);
            } else {
                moduleDeals = modulesPool[name];
            }
            this._modules[name] = moduleDeals;

            if (!moduleDeals) continue;

            if (moduleDeals.init) {
                moduleDeals.init.call(me, this._options);
            }

            // command加入命令池子
            dealCommands = moduleDeals.commands;
            for (name in dealCommands) {
                this._commands[name.toLowerCase()] = new dealCommands[name]();
            }

            // 绑定事件
            dealEvents = moduleDeals.events;
            if (dealEvents) {
                for (type in dealEvents) {
                    me.on(type, dealEvents[type]);
                }
            }

            // 渲染器
            dealRenderers = moduleDeals.renderers;

            if (dealRenderers) {

                for (type in dealRenderers) {
                    this._rendererClasses[type] = this._rendererClasses[type] || [];

                    if (Utils.isArray(dealRenderers[type])) {
                        this._rendererClasses[type] = this._rendererClasses[type].concat(dealRenderers[type]);
                    } else {
                        this._rendererClasses[type].push(dealRenderers[type]);
                    }
                }
            }

            if (moduleDeals.defaultOptions) {
                this.setDefaultOptions(moduleDeals.defaultOptions);
            }

            //添加模块的快捷键
            if (moduleDeals.commandShortcutKeys) {
                this.addCommandShortcutKeys(moduleDeals.commandShortcutKeys);
            }

            //添加邮件菜单
            if (moduleDeals.contextmenu) {
                this.addContextMenu(moduleDeals.contextmenu);
            }
        }
    },

    _garbage: function() {
        this.clearSelect();

        while (this._root.getChildren().length) {
            this._root.removeChild(0);
        }
    },

    destroy: function() {
        var modules = this._modules;

        this._resetEvents();
        this._garbage();

        for (var key in modules) {
            if (!modules[key].destroy) continue;
            modules[key].destroy.call(this);
        }
    },

    reset: function() {
        var modules = this._modules;

        this._garbage();

        for (var key in modules) {
            if (!modules[key].reset) continue;
            modules[key].reset.call(this);
        }
    }
});
Utils.extend(KityMinder, {
    _protocols: {},
    registerProtocol: function(name, protocolDeal) {
        KityMinder._protocols[name] = protocolDeal;
    }
});

var DEFAULT_TEXT = {
    'root': 'maintopic',
    'main': 'topic',
    'sub': 'topic'
};

Minder.registerInit(function() {
    this._initProtocols();
});

// 导入导出
kity.extendClass(Minder, {

    _initProtocols: function(options) {
        var protocols = this._protocols = {};
        var pool = KityMinder._protocols;
        for (var name in pool) {
            if (pool.hasOwnProperty(name))
                protocols[name] = pool[name](this);
                protocols[name].name = name;
        }
    },

    getProtocol: function(name) {
        return this._protocols[name] || null;
    },

    getSupportedProtocols: function() {
        var protocols = this._protocols;
        return Utils.keys(protocols).map(function(name) {
            return protocols[name];
        });
    },

    exportJson: function() {
        /* 导出 node 上整棵树的数据为 JSON */
        function exportNode(node) {
            var exported = {};
            exported.data = node.getData();
            var childNodes = node.getChildren();
            if (childNodes.length) {
                exported.children = [];
                for (var i = 0; i < childNodes.length; i++) {
                    exported.children.push(exportNode(childNodes[i]));
                }
            }
            return exported;
        }

        var json = exportNode(this.getRoot());

        json.template = this.getTemplate();
        json.theme = this.getTheme();
        json.version = KityMinder.version;

        return json;
    },

    importJson: function(json, params) {

        function importNode(node, json, km) {
            var data = json.data;
            node.data = {};
            for (var field in data) {
                node.setData(field, data[field]);
            }

            node.setData('text', data.text || km.getLang(DEFAULT_TEXT[node.getType()]));

            var childrenTreeData = json.children || [];
            for (var i = 0; i < childrenTreeData.length; i++) {
                var childNode = km.createNode(null, node);
                importNode(childNode, childrenTreeData[i], km);
            }
            return node;
        }

        if (!json) return;
        
        this._fire(new MinderEvent('preimport', params, false));

        // 删除当前所有节点
        while (this._root.getChildren().length) {
            this.removeNode(this._root.getChildren()[0]);
        }

        json = KityMinder.compatibility(json);

        importNode(this._root, json, this);

        this.setTemplate(json.template || 'default');
        this.setTheme(json.theme || null);
        this.refresh();

        this.fire('import', params);

        this._firePharse({
            type: 'contentchange'
        });
        this._interactChange();
    },

    exportData: function(protocolName, options) {

        var json, protocol;

        json = this.exportJson();

        // 指定了协议进行导出，需要检测协议是否支持
        if (protocolName) {
            protocol = this.getProtocol(protocolName);

            if (!protocol || !protocol.encode) {
                return Promise.reject(new Error('Not supported protocol:' + protocolName));
            }
        }

        // 导出前抛个事件
        this._fire(new MinderEvent('beforeexport', {
            json: json,
            protocolName: protocolName,
            protocol: protocol
        }));


        if (protocol) {
            return Promise.resolve(protocol.encode(json, this, options));
        } else {
            return Promise.resolve(json);
        }
    },

    importData: function(local, protocolName) {

        var json, protocol;
        var minder = this;

        // 指定了协议进行导入，需要检测协议是否支持
        if (protocolName) {
            protocol = this.getProtocol(protocolName);

            if (!protocol || !protocol.decode) {
                return Promise.reject(new Error('Not supported protocol:' + protocolName));
            }
        }

        var params = {
            local: local,
            protocolName: protocolName,
            protocol: protocol
        };

        // 导入前抛事件
        this._fire(new MinderEvent('beforeimport', params));

        return new Promise(function(resolve, reject) {

            resolve(protocol ? protocol.decode(local) : local);

        }).then(function(json) {

            minder.importJson(json, params);

            return json;

        });

    }
});
/**
 * @fileOverview
 *
 * 
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
Minder.registerInit(function(options) {
    if (options.readOnly) {
        this.setDisabled();
    }
});
kity.extendClass(Minder, {

    disable: function() {
        var me = this;
        //禁用命令
        me.bkqueryCommandState = me.queryCommandState;
        me.bkqueryCommandValue = me.queryCommandValue;
        me.queryCommandState = function(type) {
            var cmd = this._getCommand(type);
            if (cmd && cmd.enableReadOnly) {
                return me.bkqueryCommandState.apply(me, arguments);
            }
            return -1;
        };
        me.queryCommandValue = function(type) {
            var cmd = this._getCommand(type);
            if (cmd && cmd.enableReadOnly) {
                return me.bkqueryCommandValue.apply(me, arguments);
            }
            return null;
        };
        this.setStatus('readonly');
        me._interactChange();
    },

    enable: function() {
        var me = this;

        if (me.bkqueryCommandState) {
            me.queryCommandState = me.bkqueryCommandState;
            delete me.bkqueryCommandState;
        }
        if (me.bkqueryCommandValue) {
            me.queryCommandValue = me.bkqueryCommandValue;
            delete me.bkqueryCommandValue;
        }

        this.setStatus('normal');

        me._interactChange();
    }
});
/**
 * 布局支持池子管理
 */
Utils.extend(KityMinder, {
    _layout: {},

    registerLayout: function(name, layout) {
        KityMinder._layout[name] = layout;
        if (!KityMinder._defaultLayout) {
            KityMinder._defaultLayout = name;
        }
    },

    getLayoutList: function() {
        return this._layout;
    },

    getLayoutInstance: function(name) {
        var LayoutClass = KityMinder._layout[name];
        if (!LayoutClass) throw new Error('Missing Layout: ' + name);
        var layout = new LayoutClass();
        return layout;
    }
});

/**
 * MinderNode 上的布局支持
 */
kity.extendClass(MinderNode, {

    /**
     * 获得当前节点的布局名称
     *
     * @return {String}
     */
    getLayout: function() {
        var layout = this.getData('layout');

        layout = layout || (this.isRoot() ? KityMinder._defaultLayout : this.parent.getLayout());

        return layout;
    },

    setLayout: function(name) {
        if (name) {
            if (name == 'inherit') {
                this.setData('layout');
            } else {
                this.setData('layout', name);
            }
        }
        return this;
    },

    layout: function(name, duration) {

        this.setLayout(name).getMinder().layout(duration);

        return this;
    },

    getLayoutInstance: function() {
        return KityMinder.getLayoutInstance(this.getLayout());
    },

    getOrderHint: function(refer) {
        return this.parent.getLayoutInstance().getOrderHint(this);
    },

    getExpandPosition: function() {
        return this.getLayoutInstance().getExpandPosition();
    },

    /**
     * 获取当前节点相对于父节点的布局变换
     */
    getLayoutTransform: function() {
        return this._layoutTransform || new kity.Matrix();
    },

    /**
     * 第一轮布局计算后，获得的全局布局位置
     *
     * @return {[type]} [description]
     */
    getGlobalLayoutTransformPreview: function() {
        var pMatrix = this.parent ? this.parent.getLayoutTransform() : new kity.Matrix();
        var matrix = this.getLayoutTransform();
        var offset = this.getLayoutOffset();
        if (offset) {
            matrix.translate(offset.x, offset.y);
        }
        return pMatrix.merge(matrix);
    },

    getLayoutPointPreview: function() {
        return this.getGlobalLayoutTransformPreview().transformPoint(new kity.Point());
    },

    /**
     * 获取节点相对于全局的布局变换
     */
    getGlobalLayoutTransform: function() {
        if (this._globalLayoutTransform) {
            return this._globalLayoutTransform;
        } else if (this.parent) {
            return this.parent.getGlobalLayoutTransform();
        } else {
            return new kity.Matrix();
        }
    },

    /**
     * 设置当前节点相对于父节点的布局变换
     */
    setLayoutTransform: function(matrix) {
        this._layoutTransform = matrix;
        return this;
    },

    /**
     * 设置当前节点相对于全局的布局变换（冗余优化）
     */
    setGlobalLayoutTransform: function(matrix) {
        this.getRenderContainer().setMatrix(this._globalLayoutTransform = matrix);
        return this;
    },

    setVertexIn: function(p) {
        this._vertexIn = p;
    },

    setVertexOut: function(p) {
        this._vertexOut = p;
    },

    getVertexIn: function() {
        return this._vertexIn || new kity.Point();
    },

    getVertexOut: function() {
        return this._vertexOut || new kity.Point();
    },

    getLayoutVertexIn: function() {
        return this.getGlobalLayoutTransform().transformPoint(this.getVertexIn());
    },

    getLayoutVertexOut: function() {
        return this.getGlobalLayoutTransform().transformPoint(this.getVertexOut());
    },

    setLayoutVectorIn: function(v) {
        this._layoutVectorIn = v;
        return this;
    },

    setLayoutVectorOut: function(v) {
        this._layoutVectorOut = v;
        return this;
    },

    getLayoutVectorIn: function() {
        return this._layoutVectorIn || new kity.Vector();
    },

    getLayoutVectorOut: function() {
        return this._layoutVectorOut || new kity.Vector();
    },

    getLayoutBox: function() {
        var matrix = this.getGlobalLayoutTransform();
        return matrix.transformBox(this.getContentBox());
    },

    getLayoutPoint: function() {
        var matrix = this.getGlobalLayoutTransform();
        return matrix.transformPoint(new kity.Point());
    },

    getLayoutOffset: function() {
        if (!this.parent) return new kity.Point();

        // 影响当前节点位置的是父节点的布局
        var data = this.getData('layout_' + this.parent.getLayout() + '_offset');

        if (data) return new kity.Point(data.x, data.y);

        return new kity.Point();
    },

    setLayoutOffset: function(p) {
        if (!this.parent) return this;

        if (p && !this.hasLayoutOffset()) {
            var m = this.getLayoutTransform().m;
            p = p.offset(m.e, m.f);
            this.setLayoutTransform(null);
        }

        this.setData('layout_' + this.parent.getLayout() + '_offset', p ? {
            x: p.x,
            y: p.y
        } : null);

        return this;
    },

    hasLayoutOffset: function() {
        return !!this.getData('layout_' + this.parent.getLayout() + '_offset');
    },

    resetLayoutOffset: function() {
        return this.setLayoutOffset(null);
    },

    getLayoutRoot: function() {
        if (this.isLayoutRoot()) {
            return this;
        }
        return this.parent.getLayoutRoot();
    },

    isLayoutRoot: function() {
        return this.getData('layout') || this.isRoot();
    }
});

Minder.registerInit(function(options) {
    this.refresh();
});

kity.extendClass(Minder, {

    layout: function(duration) {

        this.getRoot().traverse(function(node) {
            // clear last results
            node.setLayoutTransform(null);
        });

        function layoutNode(node, round) {

            // layout all children first
            // 剪枝：收起的节点无需计算
            if (node.isExpanded() || true) {
                node.children.forEach(function(child) {
                    layoutNode(child, round);
                });
            }

            var layout = node.getLayoutInstance();
            var childrenInFlow = node.getChildren().filter(function(child) {
                return !child.hasLayoutOffset();
            });
            layout.doLayout(node, childrenInFlow, round);
        }

        // 第一轮布局
        layoutNode(this.getRoot(), 1);

        // 第二轮布局
        layoutNode(this.getRoot(), 2);

        duration = duration ? 300 : 0;

        var minder = this;
        this.applyLayoutResult(this.getRoot(), duration).then(function() {
            minder.fire('layoutallfinish');
        });

        return this.fire('layout');
    },

    refresh: function(duration) {
        this.getRoot().renderTree();
        this.layout(duration).fire('contentchange')._interactChange();
        return this;
    },

    applyLayoutResult: function(root, duration) {
        root = root || this.getRoot();
        var me = this;
        var deffered = {};

        var promise = new Promise(function(resolve, reject) {
            deffered.resolve = resolve;
            deffered.reject = reject;
        });

        var complex = root.getComplex();

        function consume() {
            if (!--complex) {
                deffered.resolve();
            }
        }

        // 节点复杂度大于 100，关闭动画
        if (complex > 200) duration = 0;

        function applyMatrix(node, matrix) {
            node.setGlobalLayoutTransform(matrix);

            me.fire('layoutapply', {
                node: node,
                matrix: matrix
            });
        }

        function apply(node, pMatrix) {
            var matrix = node.getLayoutTransform().merge(pMatrix);
            var lastMatrix = node.getGlobalLayoutTransform() || new kity.Matrix();

            var offset = node.getLayoutOffset();
            matrix.translate(offset.x, offset.y);

            matrix.m.e = Math.round(matrix.m.e);
            matrix.m.f = Math.round(matrix.m.f);


            // 如果当前有动画，停止动画
            if (node._layoutTimeline) {
                node._layoutTimeline.stop();
                node._layoutTimeline = null;
            }

            // 如果要求以动画形式来更新，创建动画
            if (duration) {
                node._layoutTimeline = new kity.Animator(lastMatrix, matrix, applyMatrix)
                    .start(node, duration, 'ease')
                    .on('finish', function() {
                        //可能性能低的时候会丢帧，手动添加一帧
                        setTimeout(function() {
                            applyMatrix(node, matrix);
                            me.fire('layoutfinish', {
                                node: node,
                                matrix: matrix
                            });
                            consume();
                        }, 150);
                    });
            }

            // 否则直接更新
            else {
                applyMatrix(node, matrix);
                me.fire('layoutfinish', {
                    node: node,
                    matrix: matrix
                });
                consume();
            }

            for (var i = 0; i < node.children.length; i++) {
                apply(node.children[i], matrix);
            }
        }

        apply(root, root.parent ? root.parent.getGlobalLayoutTransform() : new kity.Matrix());
        return promise;
    },
});


/**
 * @class Layout 布局基类，具体布局需要从该类派生
 */
var Layout = kity.createClass('Layout', {

    /**
     * @abstract
     *
     * 子类需要实现的布局算法，该算法输入一个节点，排布该节点的子节点（相对父节点的变换）
     *
     * @param  {MinderNode} node 需要布局的节点
     *
     * @example
     *
     * doLayout: function(node) {
     *     var children = node.getChildren();
     *     // layout calculation
     *     children[i].setLayoutTransform(new kity.Matrix().translate(x, y));
     * }
     */
    doLayout: function(node) {
        throw new Error('Not Implement: Layout.doLayout()');
    },

    /**
     * 对齐指定的节点
     *
     * @param {Array<MinderNode>} nodes 要对齐的节点
     * @param {string} border 对齐边界，允许取值 left, right, top, bottom
     *
     */
    align: function(nodes, border, offset) {
        var me = this;
        offset = offset || 0;
        nodes.forEach(function(node) {
            var tbox = me.getTreeBox([node]);
            var matrix = node.getLayoutTransform();
            switch (border) {
                case 'left':
                    return matrix.translate(offset - tbox.left, 0);
                case 'right':
                    return matrix.translate(offset - tbox.right, 0);
                case 'top':
                    return matrix.translate(0, offset - tbox.top);
                case 'bottom':
                    return matrix.translate(0, offset - tbox.bottom);
            }
        });
    },

    stack: function(nodes, axis, distance) {
        var me = this;

        var position = 0;

        distance = distance || function(node, next, axis) {
            return node.getStyle({
                x: 'margin-right',
                y: 'margin-bottom'
            }[axis]) + next.getStyle({
                x: 'margin-left',
                y: 'margin-top'
            }[axis]);
        };

        nodes.forEach(function(node, index, nodes) {
            var tbox = me.getTreeBox([node]);

            var size = {
                x: tbox.width,
                y: tbox.height
            }[axis];
            var offset = {
                x: tbox.left,
                y: tbox.top
            }[axis];

            var matrix = node.getLayoutTransform();

            if (axis == 'x') {
                matrix.translate(position - offset, 0);
            } else {
                matrix.translate(0, position - offset);
            }
            position += size;
            if (nodes[index + 1])
                position += distance(node, nodes[index + 1], axis);
        });
        return position;
    },

    move: function(nodes, dx, dy) {
        nodes.forEach(function(node) {
            node.getLayoutTransform().translate(dx, dy);
        });
    },

    /**
     * 工具方法：获取给点的节点所占的布局区域
     *
     * @param  {MinderNode[]} nodes 需要计算的节点
     *
     * @return {Box} 计算结果
     */
    getBranchBox: function(nodes) {
        var box = new kity.Box();
        var i, node, matrix, contentBox;
        for (i = 0; i < nodes.length; i++) {
            node = nodes[i];
            matrix = node.getLayoutTransform();
            contentBox = node.getContentBox();
            box = box.merge(matrix.transformBox(contentBox));
        }

        return box;
    },

    /**
     * 工具方法：计算给定的节点的子树所占的布局区域
     *
     * @param  {MinderNode} nodes 需要计算的节点
     *
     * @return {Box} 计算的结果
     */
    getTreeBox: function(nodes) {

        var i, node, matrix, treeBox;

        var g = KityMinder.Geometry;

        var box = {
            x: 0,
            y: 0,
            height: 0,
            width: 0
        };

        if (!(nodes instanceof Array)) nodes = [nodes];

        for (i = 0; i < nodes.length; i++) {
            node = nodes[i];
            matrix = node.getLayoutTransform();

            treeBox = node.getContentBox();

            if (node.isExpanded() && node.children.length) {
                treeBox = g.mergeBox(treeBox, this.getTreeBox(node.children));
            }

            box = g.mergeBox(box, matrix.transformBox(treeBox));
        }

        return box;
    },

    getOrderHint: function(node) {
        return [];
    }
});

var LayoutCommand = kity.createClass('LayoutCommand', {
    base: Command,

    execute: function(minder, name) {
        var nodes = minder.getSelectedNodes();
        nodes.forEach(function(node) {
            node.layout(name);
        });
    },

    queryValue: function(minder) {
        var node = minder.getSelectedNode();
        if (node) {
            return node.getData('layout');
        }
    },

    queryState: function(minder) {
        return minder.getSelectedNode() ? 0 : -1;
    }
});

var ResetLayoutCommand = kity.createClass('ResetLayoutCommand', {
    base: Command,

    execute: function(minder, name) {
        var nodes = minder.getSelectedNodes();

        if (!nodes.length) nodes = [minder.getRoot()];

        nodes.forEach(function(node) {
            node.traverse(function(child) {
                child.resetLayoutOffset();
                if (!child.isRoot()) {
                    child.setData('layout', null);
                }
            });
        });
        minder.layout(300);
    },

    enableReadOnly: true
});

KityMinder.registerModule('LayoutModule', {
    commands: {
        'layout': LayoutCommand,
        'resetlayout': ResetLayoutCommand
    },
    contextmenu: [{
        command: 'resetlayout'
    }, {
        divider: true
    }],

    commandShortcutKeys: {
        'resetlayout': 'Ctrl+Shift+L'
    }
});
var cssLikeValueMatcher = {
    left: function(value) {
        return 3 in value && value[3] ||
            1 in value && value[1] ||
            value[0];
    },
    right: function(value) {
        return 1 in value && value[1] || value[0];
    },
    top: function(value) {
        return value[0];
    },
    bottom: function(value) {
        return 2 in value && value[2] || value[0];
    }
};

Utils.extend(KityMinder, {
    _themes: {},

    /**
     * 注册一个主题
     *
     * @param  {String} name  主题的名称
     * @param  {Plain} theme 主题的样式描述
     *
     * @example
     *     KityMinder.registerTheme('default', {
     *         'root-color': 'red',
     *         'root-stroke': 'none',
     *         'root-padding': [10, 20]
     *     });
     */
    registerTheme: function(name, theme) {
        KityMinder._themes[name] = theme;
    },

    getThemeList: function() {
        return KityMinder._themes;
    }
});

kity.extendClass(Minder, {

    /**
     * 切换脑图实例上的主题
     * @param  {String} name 要使用的主题的名称
     */
    useTheme: function(name) {

        this.setTheme(name);
        this.refresh(800);

        return true;
    },

    setTheme: function(name) {
        this._theme = name || null;
        this.getRenderTarget().style.background = this.getStyle('background');
        this.fire('themechange', {
            theme: name
        });
    },

    /**
     * 获取脑图实例上的当前主题
     * @return {[type]} [description]
     */
    getTheme: function(node) {
        return this._theme || this.getOptions('defaultTheme');
    },

    getThemeItems: function(node) {
        var theme = this.getTheme(node);
        return KityMinder._themes[this.getTheme(node)];
    },

    /**
     * 获得脑图实例上的样式
     * @param  {String} item 样式名称
     */
    getStyle: function(item, node) {
        var items = this.getThemeItems(node);
        var segment, dir, selector, value, matcher;

        if (item in items) return items[item];

        // 尝试匹配 CSS 数组形式的值
        // 比如 item 为 'pading-left'
        // theme 里有 {'padding': [10, 20]} 的定义，则可以返回 20
        segment = item.split('-');
        if (segment.length < 2) return null;

        dir = segment.pop();
        item = segment.join('-');

        if (item in items) {
            value = items[item];
            if (Utils.isArray(value) && (matcher = cssLikeValueMatcher[dir])) {
                return matcher(value);
            }
            if (!isNaN(value)) return value;
        }

        return null;
    },

    /**
     * 获取指定节点的样式
     * @param  {String} name 样式名称，可以不加节点类型的前缀
     */
    getNodeStyle: function(node, name) {
        var value = this.getStyle(node.getType() + '-' + name, node);
        return value !== null ? value : this.getStyle(name, node);
    }
});

kity.extendClass(MinderNode, {
    getStyle: function(name) {
        return this.getMinder().getNodeStyle(this, name);
    }
});

KityMinder.registerModule('Theme', {
    defaultOptions: {
        defaultTheme: 'fresh-blue'
    },
    commands: {
        'theme': kity.createClass('ThemeCommand', {
            base: Command,

            execute: function(km, name) {
                return km.useTheme(name);
            },

            queryValue: function(km) {
                return km.getTheme() || 'default';
            }
        })
    }
});

Minder.registerInit(function() {
    this.setTheme();
});
Utils.extend(KityMinder, {
    
    compatibility: function(json) {

        var version = json.version || '1.1.3';

        function traverse(node, fn) {
            fn(node);
            if (node.children) node.children.forEach(function(child) {
                traverse(child, fn);
            });
        }

        /* 脑图数据升级 */
        function c_120_130(json) {
            traverse(json, function(node) {
                var data = node.data;
                delete data.layout_bottom_offset;
                delete data.layout_default_offset;
                delete data.layout_filetree_offset;
            });
        }

        /**
         * 脑图数据升级
         * v1.1.3 => v1.2.0
         * */
        function c_113_120(json) {
            // 原本的布局风格
            var ocs = json.data.currentstyle;
            delete json.data.currentstyle;

            // 为 1.2 选择模板，同时保留老版本文件的皮肤
            if (ocs == 'bottom') {
                json.template = 'structure';
                json.theme = 'snow';
            } else if (ocs == 'default') {
                json.template = 'default';
                json.theme = 'classic';
            }

            traverse(json, function(node) {
                var data = node.data;

                // 升级优先级、进度图标
                if ('PriorityIcon' in data) {
                    data.priority = data.PriorityIcon;
                    delete data.PriorityIcon;
                }
                if ('ProgressIcon' in data) {
                    data.progress = 1 + ((data.ProgressIcon - 1) << 1);
                    delete data.ProgressIcon;
                }

                // 删除过时属性
                delete data.point;
                delete data.layout;
            });
        }

        switch (version) {
            case '1.1.3':
                c_113_120(json);
            case '1.2.0':
            case '1.2.1':
                c_120_130(json);
        }

        return json;
    }
});
var Renderer = KityMinder.Renderer = kity.createClass('Renderer', {
    constructor: function(node) {
        this.node = node;
    },

    create: function(node) {
        throw new Error('Not implement: Renderer.create()');
    },

    shouldRender: function(node) {
        return true;
    },

    watchChange: function(data) {
        var changed;

        if (this.watchingData === undefined) {
            changed = true;
        } else if (this.watchingData != data) {
            changed = true;
        } else {
            changed = false;
        }

        this.watchingData = data;
    },

    shouldDraw: function(node) {
        return true;
    },

    update: function(shape, node, box) {
        if (this.shouldDraw()) this.draw(shape, node);
        return this.place(shape, node, box);
    },

    draw: function(shape, node) {
        throw new Error('Not implement: Renderer.draw()');
    },

    place: function(shape, node, box) {
        throw new Error('Not implement: Renderer.place()');
    },

    getRenderShape: function() {
        return this._renderShape || null;
    },

    setRenderShape: function(shape) {
        this._renderShape = shape;
    }
});

kity.extendClass(Minder, (function() {

    function createRendererForNode(node, registered) {
        var renderers = [];

        ['center', 'left', 'right', 'top', 'bottom', 'outline', 'outside'].forEach(function(section) {
            var before = 'before' + section;
            var after = 'after' + section;

            if (registered[before]) {
                renderers = renderers.concat(registered[before]);
            }
            if (registered[section]) {
                renderers = renderers.concat(registered[section]);
            }
            if (registered[after]) {
                renderers = renderers.concat(registered[after]);
            }
        });

        node._renderers = renderers.map(function(Renderer) {
            return new Renderer(node);
        });
    }

    return {

        renderNodeBatch: function(nodes) {
            var rendererClasses = this._rendererClasses;
            var lastBoxes = [];
            var rendererCount = 0;
            var i, j, renderer, node;

            if (!nodes.length) return;

            for (j = 0; j < nodes.length; j++) {
                node = nodes[j];
                if (!node._renderers) {
                    createRendererForNode(node, rendererClasses);
                }
                node._contentBox = new kity.Box();
                this.fire('beforerender', {
                    node: node
                });
            }

            // 所有节点渲染器数量是一致的
            rendererCount = nodes[0]._renderers.length;

            for (i = 0; i < rendererCount; i++) {

                // 获取延迟盒子数据
                for (j = 0; j < nodes.length; j++) {
                    if (typeof(lastBoxes[j]) == 'function') {
                        lastBoxes[j] = lastBoxes[j]();
                    }
                    if (!(lastBoxes[j] instanceof kity.Box)) {
                        lastBoxes[j] = new kity.Box(lastBoxes[j]);
                    }
                }

                for (j = 0; j < nodes.length; j++) {
                    node = nodes[j];
                    renderer = node._renderers[i];

                    // 合并盒子
                    if (lastBoxes[j]) {
                        node._contentBox = node._contentBox.merge(lastBoxes[j]);
                    }

                    // 判断当前上下文是否应该渲染
                    if (renderer.shouldRender(node)) {

                        // 应该渲染，但是渲染图形没创建过，需要创建
                        if (!renderer.getRenderShape()) {
                            renderer.setRenderShape(renderer.create(node));
                            if (renderer.bringToBack) {
                                node.getRenderContainer().prependShape(renderer.getRenderShape());
                            } else {
                                node.getRenderContainer().appendShape(renderer.getRenderShape());
                            }
                        }

                        // 强制让渲染图形显示
                        renderer.getRenderShape().setVisible(true);

                        // 更新渲染图形
                        lastBoxes[j] = renderer.update(renderer.getRenderShape(), node, node._contentBox);
                    }

                    // 如果不应该渲染，但是渲染图形创建过了，需要隐藏起来
                    else if (renderer.getRenderShape()) {
                        renderer.getRenderShape().setVisible(false);
                        lastBoxes[j] = null;
                    }
                }
            }

            for (j = 0; j < nodes.length; j++) {
                this.fire('noderender', {
                    node: nodes[j]
                });
            }
        },

        renderNode: function(node) {
            var rendererClasses = this._rendererClasses;
            var g = KityMinder.Geometry;
            var i, latestBox, renderer;

            if (!node._renderers) {
                createRendererForNode(node, rendererClasses);
            }

            this.fire('beforerender', {
                node: node
            });

            node._contentBox = new kity.Box();

            node._renderers.forEach(function(renderer) {

                // 判断当前上下文是否应该渲染
                if (renderer.shouldRender(node)) {

                    // 应该渲染，但是渲染图形没创建过，需要创建
                    if (!renderer.getRenderShape()) {
                        renderer.setRenderShape(renderer.create(node));
                        if (renderer.bringToBack) {
                            node.getRenderContainer().prependShape(renderer.getRenderShape());
                        } else {
                            node.getRenderContainer().appendShape(renderer.getRenderShape());
                        }
                    }

                    // 强制让渲染图形显示
                    renderer.getRenderShape().setVisible(true);

                    // 更新渲染图形
                    latestBox = renderer.update(renderer.getRenderShape(), node, node._contentBox);

                    if (typeof(latestBox) == 'function') latestBox = latestBox();

                    // 合并渲染区域
                    if (latestBox) {
                        node._contentBox = node._contentBox.merge(latestBox);
                    }
                }

                // 如果不应该渲染，但是渲染图形创建过了，需要隐藏起来
                else if (renderer.getRenderShape()) {
                    renderer.getRenderShape().setVisible(false);
                }

            });

            this.fire('noderender', {
                node: node
            });
        }
    };
})());

kity.extendClass(MinderNode, {
    render: function() {
        if (!this.attached) return;
        this.getMinder().renderNode(this);
        return this;
    },
    renderTree: function() {
        if (!this.attached) return;
        var list = [];
        this.traverse(function(node) {
            list.push(node);
        });
        this.getMinder().renderNodeBatch(list);
        return this;
    },
    getRenderer: function(type) {
        var rs = this._renderers;
        for (var i = 0; i < rs.length; i++) {
            if (rs[i].getType() == type) return rs[i];
        }
        return null;
    },
    getContentBox: function() {
        //if (!this._contentBox) this.render();
        return this.parent && this.parent.isCollapsed() ? new kity.Box() : (this._contentBox || new kity.Box());
    }
});
/* global Renderer: true */

utils.extend(KityMinder, {
    _connectProviders: {},

    _defaultConnectProvider: function(node, parent, connection) {
        connection.setPathData([
            'M', parent.getLayoutVertexOut(),
            'L', node.getLayoutVertexIn()
        ]);
    },

    registerConnectProvider: function(name, provider) {
        KityMinder._connectProviders[name] = provider;
    },

    getConnectProvider: function(name) {
        return KityMinder._connectProviders[name] || KityMinder._defaultConnectProvider;
    }
});

kity.extendClass(MinderNode, {
    getConnectProvider: function() {
        return KityMinder.getConnectProvider(this.getConnect());
    },

    getConnect: function() {
        return null;
    },

    getConnection: function() {
        return this._connection || null;
    }
});

kity.extendClass(Minder, {

    getConnectContainer: function() {
        return this._connectContainer;
    },

    createConnect: function(node) {
        if (node.isRoot()) return;

        var connection = new kity.Path();

        node._connection = connection;

        this._connectContainer.addShape(connection);
        this.updateConnect(node);
    },

    removeConnect: function(node) {
        var me = this;
        node.traverse(function(node) {
            me._connectContainer.removeShape(node._connection);
            node._connection = null;
        });
    },

    updateConnect: function(node) {

        var connection = node._connection;
        var parent = node.parent;

        if (!parent || !connection) return;

        if (parent.isCollapsed()) {
            connection.setVisible(false);
            return;
        }
        connection.setVisible(true);

        var provider = node.getConnectProvider();

        var strokeColor = node.getStyle('connect-color') || 'white',
            strokeWidth = node.getStyle('connect-width') || 2;

        connection.stroke(strokeColor, strokeWidth);

        provider(node, parent, connection, strokeWidth, strokeColor);

        if (strokeWidth % 2 === 0) {
            connection.setTranslate(0.5, 0.5);
        } else {
            connection.setTranslate(0, 0);
        }
    }
});

KityMinder.registerModule('Connect', {
    init: function() {
        this._connectContainer = new kity.Group().setId(KityMinder.uuid('minder_connect_group'));
        this.getRenderContainer().prependShape(this._connectContainer);
    },
    events: {
        'nodeattach': function(e) {
            this.createConnect(e.node);
        },
        'nodedetach': function(e) {
            this.removeConnect(e.node);
        },
        'layoutapply layoutfinish noderender': function(e) {
            this.updateConnect(e.node);
        }
    }
});
utils.extend(KityMinder, {
    _templates: {},
    registerTemplate: function(name, supports) {
        KityMinder._templates[name] = supports;
    },
    getTemplateList: function() {
        return KityMinder._templates;
    }
});

kity.extendClass(Minder, (function() {
    var originGetTheme = Minder.prototype.getTheme;
    return {
        useTemplate: function(name, duration) {
            this.setTemplate(name);
            this.refresh(duration || 800);
        },

        getTemplate: function() {
            return this._template || 'default';
        },

        setTemplate: function(name) {
            this._template = name || null;
        },

        getTemplateSupport: function(method) {
            var supports = KityMinder._templates[this.getTemplate()];
            return supports && supports[method];
        },

        getTheme: function(node) {
            var support = this.getTemplateSupport('getTheme') || originGetTheme;
            return support.call(this, node);
        }
    };
})());


kity.extendClass(MinderNode, (function() {
    var originGetLayout = MinderNode.prototype.getLayout;
    var originGetConnect = MinderNode.prototype.getConnect;
    return {
        getLayout: function() {
            var support = this.getMinder().getTemplateSupport('getLayout') || originGetLayout;
            return support.call(this, this);
        },

        getConnect: function() {
            var support = this.getMinder().getTemplateSupport('getConnect') || originGetConnect;
            return support.call(this, this);
        }
    };
})());

KityMinder.registerModule('TemplateModule', {
    commands: {
        'template': kity.createClass('TemplateCommand', {
            base: Command,

            execute: function(minder, name) {
                minder.useTemplate(name);
                minder.execCommand('camera');
            },

            queryValue: function(minder) {
                return minder.getTemplate() || 'default';
            }
        })
    }
});
//添加多语言模块
kity.extendClass( Minder, {
    getLang: function ( path ) {

        var lang = KM.LANG[ this.getOptions( 'lang' ) ];
        if ( !lang ) {
            throw Error( "not import language file" );
        }
        path = ( path || "" ).split( "." );
        for ( var i = 0, ci; ci = path[ i++ ]; ) {
            lang = lang[ ci ];
            if ( !lang ) break;
        }

        if (typeof(lang) == 'string') {
            var args = arguments;
            return lang.replace(/\{(\d+)\}/ig, function(match, gindex) {
                return args[+gindex + 1] != undefined && args[+gindex + 1].toString() || match;
            });
        }
        return lang;
    }
} );
//这里只放不是由模块产生的默认参数
KM.defaultOptions = {
    zIndex : 1000,
    lang:'zh-cn',
    readyOnly:false
};
kity.extendClass( Minder, function(){

    var ROOTKEY = 'kityminder_preference';

    //创建存储机制
    var LocalStorage = ( function () {

        var storage = window.localStorage,
            LOCAL_FILE = "localStorage";

        return {

            saveLocalData: function ( key, data ) {

                if ( storage && data) {
                    storage.setItem( key, data  );
                    return true;
                }

                return false;

            },

            getLocalData: function ( key ) {

                if ( storage ) {
                    return storage.getItem( key );
                }

                return null;

            },

            removeItem: function ( key ) {

                if (storage) storage.removeItem( key );

            }

        };

    } )();
    return {
        setPreferences: function(key,value){
            var obj = {};
            if ( Utils.isString( key ) ) {
                obj[ key ] = value;
            } else {
                obj = key;
            }
            var data = LocalStorage.getLocalData(ROOTKEY);
            if(data){
                data = JSON.parse(data);
                utils.extend(data, obj);
            }else{
                data = obj;
            }
            LocalStorage.saveLocalData(ROOTKEY,JSON.stringify(data));
        },
        getPreferences: function(key){
            var data = LocalStorage.getLocalData(ROOTKEY);
            if(data){
                data = JSON.parse(data);
                return key ? data[key] : data;
            }
            return {};
        },
        resetPreferences: function(pres){
            var str = pres ? JSON.stringify(pres) : '';
            LocalStorage.saveLocalData(str);
        }
    };

}() );
var keymap = KityMinder.keymap = (function(origin) {
    var ret = {};
    for (var key in origin) {
        if (origin.hasOwnProperty(key)) {
            ret[key] = origin[key];
            ret[key.toLowerCase()] = origin[key];
        }
    }
    var aKeyCode = 65;
    var aCharCode = 'a'.charCodeAt(0);

    // letters
    'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function(letter) {
        ret[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode);
    });

    // numbers
    var n = 9;
    do {
        ret[n.toString()] = n + 48;
    } while(--n);

    return ret;
})({
    'Backspace': 8,
    'Tab': 9,
    'Enter': 13,

    'Shift': 16,
    'Control': 17,
    'Alt': 18,
    'CapsLock': 20,

    'Esc': 27,

    'Spacebar': 32,

    'PageUp': 33,
    'PageDown': 34,
    'End': 35,
    'Home': 36,

    'Insert': 45,

    'Left': 37,
    'Up': 38,
    'Right': 39,
    'Down': 40,

    'direction': {
        37: 1,
        38: 1,
        39: 1,
        40: 1
    },

    'Del': 46,

    'NumLock': 144,

    'Cmd': 91,
    'CmdFF': 224,
    'F1': 112,
    'F2': 113,
    'F3': 114,
    'F4': 115,
    'F5': 116,
    'F6': 117,
    'F7': 118,
    'F8': 119,
    'F9': 120,
    'F10': 121,
    'F11': 122,
    'F12': 123,

    '`': 192,
    '=': 187,
    '-': 189,

    '/': 191,
    '.': 190,
    controlKeys: {
        16: 1,
        17: 1,
        18: 1,
        20: 1,
        91: 1,
        224: 1
    },
    'notContentChange': {
        13: 1,
        9: 1,

        33: 1,
        34: 1,
        35: 1,
        36: 1,

        16: 1,
        17: 1,
        18: 1,
        20: 1,
        91: 1,

        //上下左右
        37: 1,
        38: 1,
        39: 1,
        40: 1,

        113: 1,
        114: 1,
        115: 1,
        144: 1,
        27: 1
    },

    'isSelectedNodeKey': {
        //上下左右
        37: 1,
        38: 1,
        39: 1,
        40: 1,
        13: 1,
        9: 1
    }

});
/* global Layout:true */
KityMinder.registerLayout('mind', kity.createClass({
    base: Layout,

    doLayout: function(node, children) {
        var layout = this;
        var half = Math.ceil(node.children.length / 2);
        var right = [];
        var left = [];

        children.forEach(function(child) {
            if (child.getIndex() < half) right.push(child);
            else left.push(child);
        });

        var leftLayout = KityMinder.getLayoutInstance('left');
        var rightLayout = KityMinder.getLayoutInstance('right');

        leftLayout.doLayout(node, left);
        rightLayout.doLayout(node, right);

        var box = node.getContentBox();
        node.setVertexOut(new kity.Point(box.cx, box.cy));
        node.setLayoutVectorOut(new kity.Vector(0, 0));
    },

    getOrderHint: function(node) {
        var hint = [];
        var box = node.getLayoutBox();
        var offset = 5;

        hint.push({
            type: 'up',
            node: node,
            area: {
                x: box.x,
                y: box.top - node.getStyle('margin-top') - offset,
                width: box.width,
                height: node.getStyle('margin-top')
            },
            path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset]
        });

        hint.push({
            type: 'down',
            node: node,
            area: {
                x: box.x,
                y: box.bottom + offset,
                width: box.width,
                height: node.getStyle('margin-bottom')
            },
            path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset]
        });
        return hint;
    }
}));


/* global Layout:true */

[-1, 1].forEach(function (dir) {
    var name = 'filetree-' + (dir > 0 ? 'down' : 'up');

    KityMinder.registerLayout(name, kity.createClass({
        base: Layout,

        doLayout: function(parent, children, round) {
            var pBox = parent.getContentBox();
            var indent = 20;

            parent.setVertexOut(new kity.Point(pBox.left + indent, dir > 0 ? pBox.bottom : pBox.top));
            parent.setLayoutVectorOut(new kity.Vector(0, dir));

            if (!children.length) return;

            children.forEach(function(child) {
                var cbox = child.getContentBox();
                child.setLayoutTransform(new kity.Matrix());

                child.setVertexIn(new kity.Point(cbox.left, cbox.cy));
                child.setLayoutVectorIn(new kity.Vector(1, 0));
            });

            this.align(children, 'left');
            this.stack(children, 'y');

            var xAdjust = 0;
            xAdjust += pBox.left;
            xAdjust += indent;
            xAdjust += children[0].getStyle('margin-left');

            var yAdjust = 0;

            if (dir > 0) {
                yAdjust += pBox.bottom;
                yAdjust += parent.getStyle('margin-bottom');
                yAdjust += children[0].getStyle('margin-top');
            } else {
                yAdjust -= this.getTreeBox(children).bottom;
                yAdjust += pBox.top;
                yAdjust -= parent.getStyle('margin-top');
                yAdjust -= children[0].getStyle('margin-bottom');
            }

            this.move(children, xAdjust, yAdjust);

        },

        getOrderHint: function(node) {
            var hint = [];
            var box = node.getLayoutBox();
            var offset = node.getLevel() > 1 ? 3 : 5;

            hint.push({
                type: 'up',
                node: node,
                area: {
                    x: box.x,
                    y: box.top - node.getStyle('margin-top') - offset,
                    width: box.width,
                    height: node.getStyle('margin-top')
                },
                path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset]
            });

            hint.push({
                type: 'down',
                node: node,
                area: {
                    x: box.x,
                    y: box.bottom + offset,
                    width: box.width,
                    height: node.getStyle('margin-bottom')
                },
                path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset]
            });
            return hint;
        }
    }));

});
/* global Layout:true */

var layouts = ['left', 'right', 'top', 'bottom'];

layouts.forEach(function(name) {

    var axis = (name == 'left' || name == 'right') ? 'x' : 'y';
    var dir = (name == 'left' || name == 'top') ? -1 : 1;

    var oppsite = {
        'left': 'right',
        'right': 'left',
        'top': 'bottom',
        'bottom': 'top',
        'x': 'y',
        'y': 'x'
    };

    function getOrderHint(node) {
        var hint = [];
        var box = node.getLayoutBox();
        var offset = 5;

        if (axis == 'x') {
            hint.push({
                type: 'up',
                node: node,
                area: {
                    x: box.x,
                    y: box.top - node.getStyle('margin-top') - offset,
                    width: box.width,
                    height: node.getStyle('margin-top')
                },
                path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset]
            });

            hint.push({
                type: 'down',
                node: node,
                area: {
                    x: box.x,
                    y: box.bottom + offset,
                    width: box.width,
                    height: node.getStyle('margin-bottom')
                },
                path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset]
            });
        } else {
            hint.push({
                type: 'up',
                node: node,
                area: {
                    x: box.left - node.getStyle('margin-left') - offset,
                    y: box.top,
                    width: node.getStyle('margin-left'),
                    height: box.height
                },
                path: ['M', box.left - offset, box.top, 'L', box.left - offset, box.bottom]
            });

            hint.push({
                type: 'down',
                node: node,
                area: {
                    x: box.right + offset,
                    y: box.top,
                    width: node.getStyle('margin-right'),
                    height: box.height
                },
                path: ['M', box.right + offset, box.top, 'L', box.right + offset, box.bottom]
            });
        }
        return hint;
    }

    KityMinder.registerLayout(name, kity.createClass({

        base: Layout,

        doLayout: function(parent, children) {

            var pbox = parent.getContentBox();

            if (axis == 'x') {
                parent.setVertexOut(new kity.Point(pbox[name], pbox.cy));
                parent.setLayoutVectorOut(new kity.Vector(dir, 0));
            } else {
                parent.setVertexOut(new kity.Point(pbox.cx, pbox[name]));
                parent.setLayoutVectorOut(new kity.Vector(0, dir));
            }

            if (!children.length) {
                return false;
            }

            children.forEach(function(child) {
                var cbox = child.getContentBox();
                child.setLayoutTransform(new kity.Matrix());

                if (axis == 'x') {
                    child.setVertexIn(new kity.Point(cbox[oppsite[name]], cbox.cy));
                    child.setLayoutVectorIn(new kity.Vector(dir, 0));
                } else {
                    child.setVertexIn(new kity.Point(cbox.cx, cbox[oppsite[name]]));
                    child.setLayoutVectorIn(new kity.Vector(0, dir));
                }
            });

            this.align(children, oppsite[name]);
            this.stack(children, oppsite[axis]);

            var bbox = this.getBranchBox(children);
            var xAdjust, yAdjust;

            if (axis == 'x') {
                xAdjust = pbox[name];
                xAdjust += dir * parent.getStyle('margin-' + name);
                xAdjust += dir * children[0].getStyle('margin-' + oppsite[name]);

                yAdjust = pbox.bottom;
                yAdjust -= pbox.height / 2;
                yAdjust -= bbox.height / 2;
                yAdjust -= bbox.y;
            } else {
                xAdjust = pbox.right;
                xAdjust -= pbox.width / 2;
                xAdjust -= bbox.width / 2;
                xAdjust -= bbox.x;

                yAdjust = pbox[name];
                yAdjust += dir * parent.getStyle('margin-' + name);
                yAdjust += dir * children[0].getStyle('margin-' + oppsite[name]);
            }

            this.move(children, xAdjust, yAdjust);
        },

        getOrderHint: getOrderHint
    }));
});
/**
 * @fileOverview
 *
 * 鱼骨图主骨架布局
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
/* global Layout:true */
KityMinder.registerLayout('fish-bone-master', kity.createClass('FishBoneMasterLayout', {
    base: Layout,

    doLayout: function(parent, children, round) {

        var upPart = [],
            downPart = [];

        var child = children[0];
        var pBox = parent.getContentBox();

        parent.setVertexOut(new kity.Point(pBox.right, pBox.cy));
        parent.setLayoutVectorOut(new kity.Vector(1, 0));

        if (!child) return;

        var cBox = child.getContentBox();
        var pMarginRight = parent.getStyle('margin-right');
        var cMarginLeft = child.getStyle('margin-left');
        var cMarginTop = child.getStyle('margin-top');
        var cMarginBottom = child.getStyle('margin-bottom');

        children.forEach(function(child, index) {
            child.setLayoutTransform(new kity.Matrix());
            var cBox = child.getContentBox();

            if (index % 2) {
                downPart.push(child);
                child.setVertexIn(new kity.Point(cBox.left, cBox.top));
                child.setLayoutVectorIn(new kity.Vector(1, 1));
            }
            else {
                upPart.push(child);
                child.setVertexIn(new kity.Point(cBox.left, cBox.bottom));
                child.setLayoutVectorIn(new kity.Vector(1, -1));
            }

        });

        this.stack(upPart, 'x');
        this.stack(downPart, 'x');

        this.align(upPart, 'bottom');
        this.align(downPart, 'top');

        var xAdjust = pBox.right + pMarginRight + cMarginLeft;
        var yAdjustUp = pBox.cy - cMarginBottom - parent.getStyle('margin-top');
        var yAdjustDown = pBox.cy + cMarginTop + parent.getStyle('margin-bottom');

        this.move(upPart, xAdjust, yAdjustUp);
        this.move(downPart, xAdjust + cMarginLeft, yAdjustDown);

        // children.forEach(function(child, index) {
        //     var matrix = child.getLayoutTransform();
        //     var dx, dy;
        //     dx = matrix.getMatrix().e;
        //     dy = matrix.getMatrix().f;
        //     matrix.translate(-dx, -dy);
        //     matrix.rotate(index % 2 ? 45 : -45);
        //     matrix.translate(dx, dy);
        // });

    }
}));
/**
 * @fileOverview
 *
 * 
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

/* global Layout: true */
KityMinder.registerLayout('fish-bone-slave', kity.createClass('FishBoneSlaveLayout', {
    base: Layout,

    doLayout: function (parent, children, round) {

        var layout = this;
        var abs = Math.abs;
        var GOLD_CUT = 1 - 0.618;

        var pBox = parent.getContentBox();
        var vi = parent.getLayoutVectorIn();

        parent.setLayoutVectorOut(vi);

        var goldX = pBox.left + pBox.width * GOLD_CUT;
        var pout = new kity.Point(goldX, vi.y > 0 ? pBox.bottom : pBox.top);
        parent.setVertexOut(pout);

        var child = children[0];
        if (!child) return;

        var cBox = child.getContentBox();

        children.forEach(function(child, index) {
            child.setLayoutTransform(new kity.Matrix());
            child.setLayoutVectorIn(new kity.Vector(1, 0));
            child.setVertexIn(new kity.Point(cBox.left, cBox.cy));
        });

        this.stack(children, 'y');
        this.align(children, 'left');
        var xAdjust = 0, yAdjust = 0;
        
        xAdjust += pout.x;

        if (parent.getLayoutVectorOut().y < 0) {
            yAdjust -= this.getTreeBox(children).bottom;
            yAdjust += parent.getContentBox().top;
            yAdjust -= parent.getStyle('margin-top');
            yAdjust -= child.getStyle('margin-bottom');
        } else {
            yAdjust += parent.getContentBox().bottom;
            yAdjust += parent.getStyle('margin-bottom');
            yAdjust += child.getStyle('margin-top');
        }

        this.move(children, xAdjust, yAdjust);

        if (round == 2) {
            children.forEach(function(child) {
                var m = child.getLayoutTransform();
                var cbox = child.getContentBox();
                var pin = m.transformPoint(new kity.Point(cbox.left, 0));
                layout.move([child], abs(pin.y - pout.y), 0);
            });
        }
    }
}));
/**
 * @fileOverview
 *
 * 天盘模板
 *
 * @author: along
 * @copyright: bpd729@163.com, 2015
 */
KityMinder.registerLayout('tianpan', kity.createClass({
    base: Layout,

    doLayout: function (parent, children) {
        if (children.length == 0) return;

        var layout = this;
        var pbox = parent.getContentBox();

        var x, y,box;
        var _theta = 5;
        var _r = Math.max(pbox.width, 50);
        children.forEach(function (child, index) {
            child.setLayoutTransform(new kity.Matrix());
            box = layout.getTreeBox(child);
            _r = Math.max(Math.max(box.width, box.height), _r);
        })
        _r = _r / 1.5 / Math.PI;

        children.forEach(function (child, index) {
            x = _r * (Math.cos(_theta) + Math.sin(_theta) * _theta);
            y = _r * (Math.sin(_theta) - Math.cos(_theta) * _theta);

            _theta += (0.9 - index * 0.02);
            child.setLayoutVectorIn(new kity.Vector(1, 0));
            child.setVertexIn(new kity.Point(pbox.cx, pbox.cy));
            child.setLayoutTransform(new kity.Matrix());
            layout.move([child], x, y);
        });
    },

    getOrderHint: function (node) {
        var hint = [];
        var box = node.getLayoutBox();
        var offset = 5;

        hint.push({
            type: 'up',
            node: node,
            area: {
                x: box.x,
                y: box.top - node.getStyle('margin-top') - offset,
                width: box.width,
                height: node.getStyle('margin-top')
            },
            path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset]
        });

        hint.push({
            type: 'down',
            node: node,
            area: {
                x: box.x,
                y: box.bottom + offset,
                width: box.width,
                height: node.getStyle('margin-bottom')
            },
            path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset]
        });
        return hint;
    }
}));
/**
 * @fileOverview
 *
 * 提供折线相连的方法
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerConnectProvider('bezier', function(node, parent, connection) {

    // 连线起点和终点
    var po = parent.getLayoutVertexOut(),
        pi = node.getLayoutVertexIn();

    // 连线矢量和方向
    var v = parent.getLayoutVectorOut().normalize();

    var r = Math.round;
    var abs = Math.abs;

    var pathData = [];
    pathData.push('M', r(po.x), r(po.y));

    if (abs(v.x) > abs(v.y)) {
        // x - direction
        var hx = (pi.x + po.x) / 2;
        pathData.push('C', hx, po.y, hx, pi.y, pi.x, pi.y);
    } else {
        // y - direction
        var hy = (pi.y + po.y) / 2;
        pathData.push('C', po.x, hy, pi.x, hy, pi.x, pi.y);
    }

    connection.setMarker(null);
    connection.setPathData(pathData);
});
/**
 * @fileOverview
 *
 * 提供折线相连的方法
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerConnectProvider('poly', function(node, parent, connection, width) {

    // 连线起点和终点
    var po = parent.getLayoutVertexOut(),
        pi = node.getLayoutVertexIn();

    // 连线矢量和方向
    var v = parent.getLayoutVectorOut().normalize();

    var r = Math.round;
    var abs = Math.abs;

    var pathData = [];
    pathData.push('M', r(po.x), r(po.y));

    switch (true) {
        case abs(v.x) > abs(v.y) && v.x < 0:
            // left
            pathData.push('h', -parent.getStyle('margin-left'));
            pathData.push('v', pi.y - po.y);
            pathData.push('H', pi.x);
            break;

        case abs(v.x) > abs(v.y) && v.x >= 0:
            // right
            pathData.push('h', parent.getStyle('margin-right'));
            pathData.push('v', pi.y - po.y);
            pathData.push('H', pi.x);
            break;

        case abs(v.x) <= abs(v.y) && v.y < 0:
            // top
            pathData.push('v', -parent.getStyle('margin-top'));
            pathData.push('h', pi.x - po.x);
            pathData.push('V', pi.y);
            break;

        case abs(v.x) <= abs(v.y) && v.y >= 0:
            // bottom
            pathData.push('v', parent.getStyle('margin-bottom'));
            pathData.push('h', pi.x - po.x);
            pathData.push('V', pi.y);
            break;

    }

    connection.setMarker(null);
    connection.setPathData(pathData);
});
/**
 * @fileOverview
 *
 * 圆弧连线
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

var connectMarker = new kity.Marker().pipe(function() {
    var r = 7;
    var dot = new kity.Circle(r - 1);
    this.addShape(dot);
    this.setRef(r - 1, 0).setViewBox(-r, -r, r + r, r + r).setWidth(r).setHeight(r);
    this.dot = dot;
    this.node.setAttribute('markerUnits', 'userSpaceOnUse');
});

KityMinder.registerConnectProvider('arc', function(node, parent, connection, width, color) {

    var box = node.getLayoutBox(),
        pBox = parent.getLayoutBox();

    var start, end, vector;
    var abs = Math.abs;
    var pathData = [];
    var side = box.x > pBox.x ? 'right' : 'left';

    node.getMinder().getPaper().addResource(connectMarker);

    start = new kity.Point(pBox.cx, pBox.cy);
    end = side == 'left' ?
        new kity.Point(box.right + 2, box.cy) :
        new kity.Point(box.left - 2, box.cy);

    vector = kity.Vector.fromPoints(start, end);
    pathData.push('M', start);
    pathData.push('A', abs(vector.x), abs(vector.y), 0, 0, (vector.x * vector.y > 0 ? 0 : 1), end);

    connection.setMarker(connectMarker);
    connectMarker.dot.fill(color);

    connection.setPathData(pathData);
});
/**
 * @fileOverview
 *
 * 下划线连线
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerConnectProvider('under', function(node, parent, connection, width, color) {

    var box = node.getLayoutBox(),
        pBox = parent.getLayoutBox();

    var start, end, vector;
    var abs = Math.abs;
    var pathData = [];
    var side = box.x > pBox.x ? 'right' : 'left';


    var radius = node.getStyle('connect-radius');
    var underY = box.bottom + 3;
    var startY = parent.getType() == 'sub' ? pBox.bottom + 3 : pBox.cy;
    var p1, p2, p3, mx;

    if (side == 'right') {
        p1 = new kity.Point(pBox.right, startY);
        p2 = new kity.Point(box.left - 10, underY);
        p3 = new kity.Point(box.right, underY);
    } else {
        p1 = new kity.Point(pBox.left, startY);
        p2 = new kity.Point(box.right + 10, underY);
        p3 = new kity.Point(box.left, underY);
    }

    mx = (p1.x + p2.x) / 2;

    pathData.push('M', p1);
    pathData.push('C', mx, p1.y, mx, p2.y, p2);
    pathData.push('L', p3);

    connection.setMarker(null);

    connection.setPathData(pathData);
});
/**
 * @fileOverview
 *
 * "L" 连线
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerConnectProvider('l', function(node, parent, connection) {

    var po = parent.getLayoutVertexOut();
    var pi = node.getLayoutVertexIn();
    var vo = parent.getLayoutVectorOut();

    var pathData = [];
    var r = Math.round,
        abs = Math.abs;

    pathData.push('M', po.round());
    if (abs(vo.x) > abs(vo.y)) {
        pathData.push('H', r(pi.x));
    } else {
        pathData.push('V', pi.y);
    }
    pathData.push('L', pi);

    connection.setPathData(pathData);
});
/**
 * @fileOverview
 *
 * 鱼骨头主干连线
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerConnectProvider('fish-bone-master', function(node, parent, connection) {

    var pout = parent.getLayoutVertexOut(),
        pin = node.getLayoutVertexIn();

    var abs = Math.abs;

    var dy = abs(pout.y - pin.y),
        dx = abs(pout.x - pin.x);

    var pathData = [];

    pathData.push('M', pout.x, pout.y);
    pathData.push('h', dx - dy);
    pathData.push('L', pin.x, pin.y);

    connection.setMarker(null);
    connection.setPathData(pathData);
});
/**
 *
 * 圆弧连线
 *
 * @author: along
 * @copyright: bpd729@163.com , 2015
 */

var connectMarker = new kity.Marker().pipe(function () {
    var r = 7;
    var dot = new kity.Circle(r - 1);
    this.addShape(dot);
    this.setRef(r - 1, 0).setViewBox(-r, -r, r + r, r + r).setWidth(r).setHeight(r);
    this.dot = dot;
    this.node.setAttribute('markerUnits', 'userSpaceOnUse');
});

KityMinder.registerConnectProvider('arc_tp', function (node, parent, connection, width, color) {
    var end_box = node.getLayoutBox(),
        start_box = parent.getLayoutBox();


    if (node.getIndex() > 0) {
        var index = node.getIndex();
        start_box = parent.getChildren()[index - 1].getLayoutBox();
    }


    var start, end, vector;
    var abs = Math.abs;
    var pathData = [];
    var side = end_box.x > start_box.x ? 'right' : 'left';

    node.getMinder().getPaper().addResource(connectMarker);


    start = new kity.Point(start_box.cx, start_box.cy);
    end = new kity.Point(end_box.cx, end_box.cy);

    var jl = Math.sqrt(Math.abs(start.x - end.x) * Math.abs(start.x - end.x) + Math.abs(start.y - end.y) * Math.abs(start.y - end.y)); //两圆中心点距离

    jl = node.getIndex() == 0 ? jl * 0.4 : jl;

    vector = kity.Vector.fromPoints(start, end);
    pathData.push('M', start);
    pathData.push('A', jl, jl, 0, 0, 1, end);

    connection.setMarker(connectMarker);
    connectMarker.dot.fill(color);

    connection.setPathData(pathData);
});
['classic', 'classic-compact'].forEach(function(name) {
    var compact = name == 'classic-compact';

    KityMinder.registerTheme(name, {
        'background': '#3A4144 url(ui/theme/default/images/grid.png) repeat',

        'root-color': '#430',
        'root-background': '#e9df98',
        'root-stroke': '#e9df98',
        'root-font-size': 24,
        'root-padding': compact ? [10, 25] : [15, 25],
        'root-margin': compact ? [15, 25] : [30, 100],
        'root-radius': 30,
        'root-space': 10,
        'root-shadow': 'rgba(0, 0, 0, .25)',

        'main-color': '#333',
        'main-background': '#a4c5c0',
        'main-stroke': '#a4c5c0',
        'main-font-size': 16,
        'main-padding': compact ? [5, 15] : [6, 20],
        'main-margin': compact ? [5, 10] : 20,
        'main-radius': 10,
        'main-space': 5,
        'main-shadow': 'rgba(0, 0, 0, .25)',

        'sub-color': 'white',
        'sub-background': 'transparent',
        'sub-stroke': 'none',
        'sub-font-size': 12,
        'sub-padding': [5, 10],
        'sub-margin': compact ? [5, 10] : [15, 20],
        'sub-tree-margin': 30,
        'sub-radius': 5,
        'sub-space': 5,

        'connect-color': 'white',
        'connect-width': 2,
        'main-connect-width': 3,
        'connect-radius': 5,

        'selected-background': 'rgb(254, 219, 0)',
        'selected-stroke': 'rgb(254, 219, 0)',
        'selected-color': 'black',

        'marquee-background': 'rgba(255,255,255,.3)',
        'marquee-stroke': 'white',

        'drop-hint-color': 'yellow',
        'sub-drop-hint-width': 2,
        'main-drop-hint-width': 4,
        'root-drop-hint-width': 4,

        'order-hint-area-color': 'rgba(0, 255, 0, .5)',
        'order-hint-path-color': '#0f0',
        'order-hint-path-width': 1,

        'text-selection-color': 'rgb(27,171,255)',
        'line-height':1.5
    });
});


['snow', 'snow-compact'].forEach(function(name) {
    var compact = name == 'snow-compact';

    KityMinder.registerTheme(name, {
        'background': '#3A4144 url(ui/theme/default/images/grid.png) repeat',

        'root-color': '#430',
        'root-background': '#e9df98',
        'root-stroke': '#e9df98',
        'root-font-size': 24,
        'root-padding': compact ? [5, 10] : [15, 25],
        'root-margin': compact ? 15 : 30,
        'root-radius': 5,
        'root-space': 10,
        'root-shadow': 'rgba(0, 0, 0, .25)',

        'main-color': '#333',
        'main-background': '#a4c5c0',
        'main-stroke': '#a4c5c0',
        'main-font-size': 16,
        'main-padding': compact ? [4, 10] : [6, 20],
        'main-margin': compact ? [5, 10] : [20, 40],
        'main-radius': 5,
        'main-space': 5,
        'main-shadow': 'rgba(0, 0, 0, .25)',

        'sub-color': 'black',
        'sub-background': 'white',
        'sub-stroke': 'white',
        'sub-font-size': 12,
        'sub-padding': [5, 10],
        'sub-margin': compact ? [5, 10] : [10, 20],
        'sub-radius': 5,
        'sub-space': 5,

        'connect-color': 'white',
        'connect-width': 2,
        'main-connect-width': 3,
        'connect-radius': 5,

        'selected-background': 'rgb(254, 219, 0)',
        'selected-stroke': 'rgb(254, 219, 0)',

        'marquee-background': 'rgba(255,255,255,.3)',
        'marquee-stroke': 'white',

        'drop-hint-color': 'yellow',
        'drop-hint-width': 4,

        'order-hint-area-color': 'rgba(0, 255, 0, .5)',
        'order-hint-path-color': '#0f0',
        'order-hint-path-width': 1,

        'text-selection-color': 'rgb(27,171,255)',
        'line-height':1.5
    });
});
(function() {
    function hsl(h, s, l) {
        return kity.Color.createHSL(h, s, l);
    }

    function generate(h, compat) {
        return {
            'background': '#fbfbfb',

            'root-color': 'white',
            'root-background': hsl(h, 37, 60),
            'root-stroke': hsl(h, 37, 60),
            'root-font-size': 16,
            'root-padding': compat ? [6, 12] : [12, 24],
            'root-margin': compat ? 10 : [30, 100],
            'root-radius': 5,
            'root-space': 10,

            'main-color': 'black',
            'main-background': hsl(h, 33, 95),
            'main-stroke': hsl(h, 37, 60),
            'main-stroke-width': 1,
            'main-font-size': 14,
            'main-padding': [6, 20],
            'main-margin': compat ? 8 : 20,
            'main-radius': 3,
            'main-space': 5,

            'sub-color': 'black',
            'sub-background': 'transparent',
            'sub-stroke': 'none',
            'sub-font-size': 12,
            'sub-padding': compat ? [3, 5] : [5, 10],
            'sub-margin': compat ? [4, 8] : [15, 20],
            'sub-radius': 5,
            'sub-space': 5,

            'connect-color': hsl(h, 37, 60),
            'connect-width': 1,
            'connect-radius': 5,

            'selected-stroke': hsl(h, 26, 30),
            'selected-stroke-width': '3',

            'marquee-background': hsl(h, 100, 80).set('a', 0.1),
            'marquee-stroke': hsl(h, 37, 60),

            'drop-hint-color': hsl(h, 26, 35),
            'drop-hint-width': 5,

            'order-hint-area-color': hsl(h, 100, 30).set('a', 0.5),
            'order-hint-path-color': hsl(h, 100, 25),
            'order-hint-path-width': 1,

            'text-selection-color': hsl(h, 100, 20),
            'line-height':1.5
        };
    }

    var plans = {
        red: 0,
        soil: 25,
        green: 122,
        blue: 204,
        purple: 246,
        pink: 334
    };
    var name;
    for (name in plans) {
        KityMinder.registerTheme('fresh-' + name, generate(plans[name]));
        KityMinder.registerTheme('fresh-' + name + '-compat', generate(plans[name], true));
    }

})();
KityMinder.registerTheme('fish', {
    'background': '#3A4144 url(ui/theme/default/images/grid.png) repeat',

    'root-color': '#430',
    'root-background': '#e9df98',
    'root-stroke': '#e9df98',
    'root-font-size': 24,
    'root-padding': [35, 35],
    'root-margin': 30,
    'root-radius': 100,
    'root-space': 10,
    'root-shadow': 'rgba(0, 0, 0, .25)',

    'main-color': '#333',
    'main-background': '#a4c5c0',
    'main-stroke': '#a4c5c0',
    'main-font-size': 16,
    'main-padding': [6, 20],
    'main-margin': [20, 20],
    'main-radius': 5,
    'main-space': 5,
    'main-shadow': 'rgba(0, 0, 0, .25)',

    'sub-color': 'black',
    'sub-background': 'white',
    'sub-stroke': 'white',
    'sub-font-size': 12,
    'sub-padding': [5, 10],
    'sub-margin': [10],
    'sub-radius': 5,
    'sub-space': 5,

    'connect-color': 'white',
    'connect-width': 3,
    'main-connect-width': 3,
    'connect-radius': 5,

    'selected-background': 'rgb(254, 219, 0)',
    'selected-stroke': 'rgb(254, 219, 0)',

    'marquee-background': 'rgba(255,255,255,.3)',
    'marquee-stroke': 'white',

    'drop-hint-color': 'yellow',
    'drop-hint-width': 4,

    'order-hint-area-color': 'rgba(0, 255, 0, .5)',
    'order-hint-path-color': '#0f0',
    'order-hint-path-width': 1,

    'text-selection-color': 'rgb(27,171,255)',
    'line-height':1.5
});
KityMinder.registerTheme('wire', {
    'background': 'black',

    'color': '#999',
    'stroke': 'none',
    'padding': 10,
    'margin': 20,
    'font-size': 14,

    'connect-color': '#999',
    'connect-width': 1,

    'selected-background': '#999',
    'selected-color': 'black',

    'marquee-background': 'rgba(255,255,255,.3)',
    'marquee-stroke': 'white',

    'drop-hint-color': 'yellow',
    'sub-drop-hint-width': 2,
    'main-drop-hint-width': 4,
    'root-drop-hint-width': 4,

    'order-hint-area-color': 'rgba(0, 255, 0, .5)',
    'order-hint-path-color': '#0f0',
    'order-hint-path-width': 1,

    'text-selection-color': 'rgb(27,171,255)',
    'line-height':1.5
});
['tianpan', 'tianpan-compact'].forEach(function (name) {
    var compact = name == 'tianpan-compact';

    KityMinder.registerTheme(name, {
        'background': '#3A4144 url(ui/theme/default/images/grid.png) repeat',

        'root-color': '#430',
        'root-background': '#e9df98',
        'root-stroke': '#e9df98',
        'root-font-size': 25,
        'root-padding': compact ? 15 : 20,
        'root-margin': compact ? [15, 25] : 100,
        'root-radius': 30,
        'root-space': 10,
        'root-shadow': 'rgba(0, 0, 0, .25)',
        'root-shape': 'circle',

        'main-color': '#333',
        'main-background': '#a4c5c0',
        'main-stroke': '#a4c5c0',
        'main-font-size': 15,
        'main-padding': compact ? 10 : 12,
        'main-margin': compact ? 10 : 12,
        'main-radius': 10,
        'main-space': 5,
        'main-shadow': 'rgba(0, 0, 0, .25)',
        'main-shape': 'circle',

        'sub-color': '#333',
        'sub-background': '#99ca6a',
        'sub-stroke': '#a4c5c0',
        'sub-font-size': 13,
        'sub-padding': 5,
        'sub-margin': compact ? 6 : 10,
        'sub-tree-margin': 30,
        'sub-radius': 5,
        'sub-space': 5,
        'sub-shadow': 'rgba(0, 0, 0, .25)',
        'sub-shape': 'circle',

        'connect-color': 'white',
        'connect-width': 2,
        'main-connect-width': 3,
        'connect-radius': 5,

        'selected-background': 'rgb(254, 219, 0)',
        'selected-stroke': 'rgb(254, 219, 0)',
        'selected-color': 'black',

        'marquee-background': 'rgba(255,255,255,.3)',
        'marquee-stroke': 'white',

        'drop-hint-color': 'yellow',
        'sub-drop-hint-width': 2,
        'main-drop-hint-width': 4,
        'root-drop-hint-width': 4,

        'order-hint-area-color': 'rgba(0, 255, 0, .5)',
        'order-hint-path-color': '#0f0',
        'order-hint-path-width': 1,

        'text-selection-color': 'rgb(27,171,255)',
        'line-height': 1.4
    });
});
/**
 * @fileOverview
 *
 * 默认模板 - 脑图模板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerTemplate('default', {

    getLayout: function(node) {

        if (node.getData('layout')) return node.getData('layout');

        var level = node.getLevel();

        // 根节点
        if (level === 0) {
            return 'mind';
        }

        // 一级节点
        if (level === 1) {
            return node.getLayoutPointPreview().x > 0 ? 'right': 'left';
        }

        return node.parent.getLayout();
    },

    getConnect: function(node) {
        if (node.getLevel() == 1) return 'arc';
        return 'under';
    }
});
/**
 * @fileOverview
 *
 * 组织结构图模板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerTemplate('structure', {

    getLayout: function(node) {
        return node.getData('layout') || 'bottom';
    },

    getConnect: function(node) {
        return 'poly';
    }
});
/**
 * @fileOverview
 *
 * 文件夹模板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerTemplate('filetree', {

    getLayout: function(node) {
        if (node.getData('layout')) return node.getData('layout');
        if (node.isRoot()) return 'bottom';

        return 'filetree-down';
    },

    getConnect: function(node) {
        if (node.getLevel() == 1) {
            return 'poly';
        }
        return 'l';
    }
});
/**
 * @fileOverview
 *
 * 往右布局结构模板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerTemplate('right', {

    getLayout: function(node) {
        return node.getData('layout') || 'right';
    },

    getConnect: function(node) {
        if (node.getLevel() == 1) return 'arc';
        return 'bezier';
    }
});
/**
 * @fileOverview
 *
 * 默认模板 - 鱼骨头模板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerTemplate('fish-bone', {

    getLayout: function(node) {

        if (node.getData('layout')) return node.getData('layout');

        var level = node.getLevel();

        // 根节点
        if (level === 0) {
            return 'fish-bone-master';
        }

        // 一级节点
        if (level === 1) {
            return 'fish-bone-slave';
        }

        return node.getLayoutPointPreview().y > 0 ? 'filetree-up': 'filetree-down';
    },

    getConnect: function(node) {
        switch (node.getLevel()) {
            case 1: return 'fish-bone-master';
            case 2: return 'line';
            default: return 'l';
        }
    }
});
/**
 * @fileOverview
 *
 * 天盘模板
 *
 * @author: along
 * @copyright: bpd729@163.com, 2015
 */

KityMinder.registerTemplate('tianpan', {
    getLayout: function (node) {
        if (node.getData('layout')) return node.getData('layout');
        var level = node.getLevel();

        // 根节点
        if (level === 0) {
            return 'tianpan';
        }

        return node.parent.getLayout();
    },

    getConnect: function (node) {
        return 'arc_tp';
    }
});
var AppendChildCommand = kity.createClass('AppendChildCommand', {
    base: Command,
    execute: function(km, text) {
        var parent = km.getSelectedNode();
        if (!parent) {
            return null;
        }
        text = text || km.getLang('topic');
        parent.expand();
        var node = km.createNode(text, parent);
        km.select(node, true);
        node.render();
        km.layout(600);
    },
    queryState: function(km) {
        var selectedNode = km.getSelectedNode();
        return selectedNode ? 0 : -1;
    }
});

var AppendSiblingCommand = kity.createClass('AppendSiblingCommand', {
    base: Command,
    execute: function(km, text) {
        var sibling = km.getSelectedNode();
        var parent = sibling.parent;
        if (!parent) {
            return km.execCommand('AppendChildNode', text);
        }
        text = text || km.getLang('topic');
        var node = km.createNode(text, parent, sibling.getIndex() + 1);
        km.select(node, true);
        node.render();
        km.layout(600);
    },
    queryState: function(km) {
        var selectedNode = km.getSelectedNode();
        return selectedNode ? 0 : -1;
    }
});

var RemoveNodeCommand = kity.createClass('RemoverNodeCommand', {
    base: Command,
    execute: function(km, text) {
        var nodes = km.getSelectedNodes();
        var ancestor = MinderNode.getCommonAncestor.apply(null, nodes);

        nodes.forEach(function(node) {
            if (!node.isRoot()) km.removeNode(node);
        });

        km.select(ancestor || km.getRoot(), true);
        km.layout(600);
    },
    queryState: function(km) {
        var selectedNode = km.getSelectedNode();
        return selectedNode ? 0 : -1;
    }
});

var EditNodeCommand = kity.createClass('EditNodeCommand', {
    base: Command,
    execute: function(km) {
        var selectedNode = km.getSelectedNode();
        if (!selectedNode) {
            return null;
        }
        km.select(selectedNode, true);
        km.textEditNode(selectedNode);
    },
    queryState: function(km) {
        var selectedNode = km.getSelectedNode();
        if (!selectedNode) {
            return -1;
        }
        else {
            return 0;
        }
    },
    isNeedUndo: function() {
        return false;
    }
});

var $importDialog;
var $exportDialog;

var ImportTextNode = kity.createClass('ImportTextNode', {
    base: Command,
    execute: function(km, text) {
        if (!$importDialog) {
            var plainProtocol = km.getProtocol('plain');

            var height = 400;
            $importDialog = new FUI.Dialog({
                width: 600,
                height: height,
                prompt: true,
                caption: km.getLang('ui.importtextnode')
            }).appendTo(document.getElementById('content-wrapper'));

            var $dialogBody = $($importDialog.getBodyElement());

            $dialogBody.html([
                '<textarea style="width: 100%; height: ' + (height - 130) + 'px"></textarea>'
            ].join(''));

            var $editor = $dialogBody.find('textarea');
            var $ok = $importDialog.getButton(0);
            var $errorMsg = $('<span class="validate-error"></span>');
            var rootNode;

            function check(value) {
                var error;
                if (!value) {
                    error = '内容不能为空';
                }
                else {
                    try {
                        rootNode = plainProtocol.decode(value, true);
                    }
                    catch (ex) {
                        error = '文本格式解析错误. ' + ex.message;
                    }
                }

                if (error) {
                    $editor.addClass('validate-error');
                    $errorMsg.text(error);
                    $ok.disable();
                }
                else {
                    $editor.removeClass('validate-error');
                    $errorMsg.text('');
                    $ok.enable();
                }
            }

            $editor.after($errorMsg);

            $editor.on('input', function() {
                check($editor.val());
            });
            $editor.on('keydown keyup', function(e) {
                if (e.keyCode === 9 && e.type === 'keydown') {
                    e.preventDefault();
                    var s = this.selectionStart;
                    this.value = this.value.substring(0, this.selectionStart) + '\t' +
                        this.value.substring(this.selectionEnd);
                    this.selectionEnd = s + 1;
                }
                if (e.keyCode === 13 && !e.shiftKey) {
                    return;
                }
                e.stopPropagation();
            });
            $importDialog.on('open', function() {
                check($editor.val());
                setTimeout(function() {
                    $editor[0].focus();
                }, 10);
            });
            $importDialog.on('ok', function() {
                var _selectedNodes = [];

                function appendChild(parent, child, index) {
                    if (!parent || !child) {
                        return;
                    }
                    var kmNode = km.createNode(child.data.text, parent);
                    if (child.children) {
                        child.children.forEach(function(item) {
                            appendChild(kmNode, item);
                        });
                    }
                    kmNode.render();
                    return kmNode;
                }

                var selectedNode = km.getSelectedNode();
                if (rootNode && rootNode.children && selectedNode) {
                    selectedNode.expand();
                    rootNode.children.forEach(function(child) {
                        _selectedNodes.push(appendChild(selectedNode, child));
                    });
                }
                km.select(_selectedNodes, true);
                km.layout(300);

                $editor.val('');
                _selectedNodes = null;
                rootNode = null;
            });
        }
        $importDialog.open();
    },
    queryState: function(km) {
        var selectedNode = km.getSelectedNode();
        return selectedNode ? 0 : -1;
    }
});

var ExportTextNode = kity.createClass('ExportTextNode', {
    base: Command,
    execute: function(km, text) {
        if (!$exportDialog) {
            var plainProtocol = km.getProtocol('plain');

            var height = 400;
            $exportDialog = new FUI.Dialog({
                width: 600,
                height: height,
                prompt: true,
                caption: km.getLang('ui.exporttextnode')
            }).appendTo(document.getElementById('content-wrapper'));

            var $dialogBody = $($exportDialog.getBodyElement());

            $dialogBody.html([
                '<textarea style="width: 100%; height: ' + (height - 130) + 'px"></textarea>'
            ].join(''));

            var $editor = $dialogBody.find('textarea');

            $editor.on('keydown keyup', function(e) {
                if (e.keyCode === 9 && e.type === 'keydown') {
                    e.preventDefault();
                    var s = this.selectionStart;
                    this.value = this.value.substring(0, this.selectionStart) + '\t' +
                        this.value.substring(this.selectionEnd);
                    this.selectionEnd = s + 1;
                }
                if (e.keyCode === 13 && !e.shiftKey) {
                    return;
                }
                e.stopPropagation();
            });

            $exportDialog.on('open', function() {
                $editor.val(plainProtocol.encode(km.getSelectedNode()));
                setTimeout(function() {
                    $editor[0].focus();
                }, 10);
            });
        }
        $exportDialog.open();
    },
    queryState: function(km) {
        var selectedNode = km.getSelectedNode();
        return selectedNode ? 0 : -1;
    }
});

KityMinder.registerModule('NodeModule', function() {
    return {

        commands: {
            'AppendChildNode': AppendChildCommand,
            'AppendSiblingNode': AppendSiblingCommand,
            'ImportTextNode': ImportTextNode,
            'ExportTextNode': ExportTextNode,
            'RemoveNode': RemoveNodeCommand,
            'EditNode': EditNodeCommand
        },

        'contextmenu': [{
            command: 'appendsiblingnode'
        }, {
            command: 'appendchildnode'
        }, {
            command: 'editnode'
        }, {
            command: 'removenode'
        }, {
            command: 'importtextnode'
        }, {
            command: 'exporttextnode'
        }, {
            divider: 1
        }],

        'commandShortcutKeys': {
            'appendsiblingnode': 'normal::Enter',
            'appendchildnode': 'normal::Insert|Tab',
            'editnode': 'normal::F2',
            'removenode': 'normal::Del|Backspace'
        }
    };
});

/* global Renderer: true */

var FONT_ADJUST = {
    '微软雅黑,Microsoft YaHei': -0.15,
    'arial black,avant garde': -0.17,
    'default': -0.15
};

var TextRenderer = KityMinder.TextRenderer = kity.createClass('TextRenderer', {
    base: Renderer,

    create: function() {
        return new kity.Group().setId(KityMinder.uuid('node_text'));
    },

    update: function(textGroup, node) {

        function s(name) {
            return node.getData(name) || node.getStyle(name);
        }

        var textArr = node.getText(true);

        var lineHeight = node.getStyle('line-height');

        var fontSize = s('font-size');
        var fontFamily = s('font-family') || 'default';

        var height = (lineHeight * fontSize) * textArr.length - (lineHeight - 1) * fontSize;
        var yStart = -height / 2;

        var adjust = FONT_ADJUST[fontFamily] || 0;

        textGroup.setTranslate(0, adjust * fontSize);

        var rBox = new kity.Box(),
            r = Math.round;

        this.setTextStyle(node, textGroup);

        var textLength = textArr.length;

        var textGroupLength = textGroup.getItems().length;

        if(textLength < textGroupLength){
            for( var i = textLength,ci;ci = textGroup.getItem(i);){
                textGroup.removeItem(i);
            }
        }else if(textLength > textGroupLength){
            var length = textLength - textGroupLength;
            for(var i = 0;i < length;i++){
                var textShape = new kity.Text()
                    .setAttr('text-rendering', 'inherit');
                if (kity.Browser.ie) {
                    textShape.setVerticalAlign('top');
                } else {
                    textShape.setAttr('dominant-baseline', 'text-before-edge');
                }
                textGroup.addItem(textShape);
            }
        }


        for (var i = 0, text, textShape;
            (text = textArr[i], textShape = textGroup.getItem(i)); i++) {
            textShape.setContent(text);
        }

        this.setTextStyle(node, textGroup);

        var textHash = node.getText() + [s('font-size'), s('font-name'), s('font-weight'), s('font-style')].join('/');

        if (node._currentTextHash == textHash && node._currentTextGroupBox) return node._currentTextGroupBox;

        node._currentTextHash = textHash;

        return function() {
            textGroup.eachItem(function(i, textShape) {
                var y = yStart + i * fontSize * lineHeight;

                textShape.setY(y);

                rBox = rBox.merge(new kity.Box(0, y, textShape.getBoundaryBox().width || 1, fontSize));
            });

            //为了让文字在圆中垂直居中
            var w = 0;
            var shape = node.getStyle('shape');
            if(shape && shape=='circle'){
                w = Math.max(rBox.width,rBox.height)/2-rBox.height/2;
            }
            var nBox = new kity.Box(r(rBox.x), r(rBox.y-w), r(rBox.width), r(rBox.height));

            node._currentTextGroupBox = nBox;
            return nBox;
        };

    },

    setTextStyle: function(node, text) {
        var hooks = TextRenderer._styleHooks;
        hooks.forEach(function(hook) {
            hook(node, text);
        });
    }
});

utils.extend(TextRenderer, {
    _styleHooks: [],

    registerStyleHook: function(fn) {
        TextRenderer._styleHooks.push(fn);
    }
});

kity.extendClass(MinderNode, {
    getTextGroup: function() {
        return this.getRenderer('TextRenderer').getRenderShape();
    }
});
KityMinder.registerModule('text', {
    'renderers': {
        center: TextRenderer
    }
});
/* global Renderer: true */

KityMinder.registerModule('Expand', function() {
    var minder = this;
    var EXPAND_STATE_DATA = 'expandState',
        STATE_EXPAND = 'expand',
        STATE_COLLAPSE = 'collapse';

    // 将展开的操作和状态读取接口拓展到 MinderNode 上
    kity.extendClass(MinderNode, {

        /**
         * 展开节点
         * @param  {Policy} policy 展开的策略，默认为 KEEP_STATE
         */
        expand: function() {
            this.setData(EXPAND_STATE_DATA, STATE_EXPAND);
            return this;
        },

        /**
         * 收起节点
         */
        collapse: function() {
            this.setData(EXPAND_STATE_DATA, STATE_COLLAPSE);
            return this;
        },

        /**
         * 判断节点当前的状态是否为展开
         */
        isExpanded: function() {
            var expanded = this.getData(EXPAND_STATE_DATA) !== STATE_COLLAPSE;
            return expanded && (this.isRoot() || this.parent.isExpanded());
        },

        /**
         * 判断节点当前的状态是否为收起
         */
        isCollapsed: function() {
            return !this.isExpanded();
        }
    });

    var ExpandCommand = kity.createClass('ExpandCommand', {
        base: Command,

        execute: function(km, justParents) {
            var node = km.getSelectedNode();
            if (!node) return;
            if (justParents) {
                node = node.parent;
            }
            while(node.parent) {
                node.expand();
                node = node.parent;
            }
            node.renderTree();
            km.layout(100);
        },

        queryState: function(km) {
            return km.getSelectedNode() ? 0 : -1;
        }
    });

    var ExpandToLevelCommand = kity.createClass('ExpandToLevelCommand', {
        base: Command,
        execute: function(km, level) {
            km.getRoot().traverse(function(node) {
                if (node.getLevel() < level) node.expand();
                if (node.getLevel() == level) node.collapse();
            });
            km.refresh(100);
        },
        enableReadOnly: true
    });

    var Expander = kity.createClass('Expander', {
        base: kity.Group,

        constructor: function(node) {
            this.callBase();
            this.radius = 6;
            this.outline = new kity.Circle(this.radius).stroke('gray').fill('white');
            this.sign = new kity.Path().stroke('gray');
            this.addShapes([this.outline, this.sign]);
            this.initEvent(node);
            this.setId(KityMinder.uuid('node_expander'));
            this.setStyle('cursor', 'pointer');
        },

        initEvent: function(node) {
            this.on('mousedown', function(e) {
                if (node.isExpanded()) {
                    node.collapse();
                } else {
                    node.expand();
                }
                node.renderTree().getMinder().layout(100);
                node.getMinder().fire('contentchange');
                e.stopPropagation();
                e.preventDefault();
            });
            this.on('dblclick click mouseup', function(e) {
                e.stopPropagation();
                e.preventDefault();
            });
        },

        setState: function(state) {
            if (state == 'hide') {
                this.setVisible(false);
                return;
            }
            this.setVisible(true);
            var pathData = ['M', 1.5 - this.radius, 0, 'L', this.radius - 1.5, 0];
            if (state == STATE_COLLAPSE) {
                pathData.push(['M', 0, 1.5 - this.radius, 'L', 0, this.radius - 1.5]);
            }
            this.sign.setPathData(pathData);
        }
    });

    var ExpanderRenderer = kity.createClass('ExpanderRenderer', {
        base: Renderer,

        create: function(node) {
            if (node.isRoot()) return;
            this.expander = new Expander(node);
            node.getRenderContainer().prependShape(this.expander);
            node.expanderRenderer = this;
            this.node = node;
            return this.expander;
        },

        shouldRender: function(node) {
            return !node.isRoot();
        },

        update: function(expander, node, box) {
            if (!node.parent) return;

            var visible = node.parent.isExpanded();

            expander.setState(visible && node.children.length ? node.getData(EXPAND_STATE_DATA) : 'hide');

            var vector = node.getLayoutVectorIn().normalize(expander.radius + node.getStyle('stroke-width'));
            var position = node.getVertexIn().offset(vector.reverse());

            this.expander.setTranslate(position);
        }
    });
    return {
        commands: {
            'expand': ExpandCommand,
            'expandtolevel': ExpandToLevelCommand
        },
        events: {
            'layoutapply': function(e) {
                var r = e.node.getRenderer('ExpanderRenderer');
                if (r.getRenderShape()) {
                    r.update(r.getRenderShape(), e.node);
                }
            },
            'beforerender': function(e) {
                var node = e.node;
                var visible = !node.parent || node.parent.isExpanded();
                var minder = this;

                node.getRenderContainer().setVisible(visible);
                if (!visible) e.stopPropagation();
            },
            'normal.keydown': function(e) {
                if (this.getStatus() == 'textedit') return;
                if (e.originEvent.keyCode == keymap['/']) {
                    var node = this.getSelectedNode();
                    if (!node || node == this.getRoot()) return;
                    var expanded = node.isExpanded();
                    this.getSelectedNodes().forEach(function(node) {
                        if (expanded) node.collapse();
                        else node.expand();
                        node.renderTree();
                    });
                    this.layout(100);
                    this.fire('contentchange');
                    e.preventDefault();
                    e.stopPropagationImmediately();
                }
                if (e.isShortcutKey('Alt+`')) {
                    this.execCommand('expandtolevel', 9999);
                }
                for (var i = 1; i < 6; i++) {
                    if (e.isShortcutKey('Alt+' + i)) {
                        this.execCommand('expandtolevel', i);
                    }
                }
            }
        },
        renderers: {
            outside: ExpanderRenderer
        },
        contextmenu: [{
            command: 'expandtoleaf',
            query: function() {
                return !minder.getSelectedNode();
            },
            fn: function(minder) {
                minder.execCommand('expandtolevel', 9999);
            }
        }, {
            command: 'expandtolevel1',
            query: function() {
                return !minder.getSelectedNode();
            },
            fn: function(minder) {
                minder.execCommand('expandtolevel', 1);
            }
        }, {
            command: 'expandtolevel2',
            query: function() {
                return !minder.getSelectedNode();
            },
            fn: function(minder) {
                minder.execCommand('expandtolevel', 2);
            }
        },{
            command: 'expandtolevel3',
            query: function() {
                return !minder.getSelectedNode();
            },
            fn: function(minder) {
                minder.execCommand('expandtolevel', 3);
            }
        }, {
            divider: true
        }]
    };
});
/* global Renderer: true */


var OutlineRenderer = kity.createClass('OutlineRenderer', {
    base: Renderer,

    create: function(node) {

        var outline = new kity.Rect()
            .setId(KityMinder.uuid('node_outline'));

        this.bringToBack = true;

        return outline;
    },

    update: function(outline, node, box) {

        //增加圆形update、待更好解决方案
        var shape = node.getStyle('shape');
        if(shape){
            if(shape=='circle'){
                return updateCircle(outline, node, box);
            }
        }
        var paddingLeft = node.getStyle('padding-left'),
            paddingRight = node.getStyle('padding-right'),
            paddingTop = node.getStyle('padding-top'),
            paddingBottom = node.getStyle('padding-bottom');

        var outlineBox = {
            x: box.x - paddingLeft,
            y: box.y - paddingTop,
            width: box.width + paddingLeft + paddingRight,
            height: box.height + paddingTop + paddingBottom
        };

        var prefix = node.isSelected() ? 'selected-' : '';

        outline
            .setPosition(outlineBox.x, outlineBox.y)
            .setSize(outlineBox.width, outlineBox.height)
            .setRadius(node.getStyle('radius'))
            .fill(node.getData('background') || node.getStyle(prefix + 'background') || node.getStyle('background'))
            .stroke(node.getStyle(prefix + 'stroke' || node.getStyle('stroke')),
                node.getStyle(prefix + 'stroke-width'));

        return new kity.Box(outlineBox);
    }
});


//圆
function updateCircle(outline, node, box){

    var paddingLeft = node.getStyle('padding-left'),
        paddingRight = node.getStyle('padding-right'),
        paddingTop = node.getStyle('padding-top'),
        paddingBottom = node.getStyle('padding-bottom');

    var width= Math.max(box.width,box.height);

    var outlineBox = {
        x: box.x - paddingLeft,
        y: box.y - paddingTop,
        width: width + paddingLeft + paddingRight,
        height: width + paddingTop + paddingBottom
    };

    var prefix = node.isSelected() ? 'selected-' : '';

    width= Math.max(outlineBox.width,outlineBox.height);

    outline
        .setPosition(outlineBox.x, outlineBox.y)
        .setSize(width, width)
        .setRadius(width/2)
        .fill(node.getData('background') || node.getStyle(prefix + 'background') || node.getStyle('background'))
        .stroke(node.getStyle(prefix + 'stroke' || node.getStyle('stroke')),
            node.getStyle(prefix + 'stroke-width'));
    return new kity.Box(outlineBox);
}


var ShadowRenderer = kity.createClass('ShadowRenderer', {
    base: Renderer,

    create: function(node) {
        this.bringToBack = true;
        return new kity.Rect();
    },

    shouldRender: function(node) {
        return node.getStyle('shadow');
    },

    update: function(shadow, node, box) {
        shadow.setPosition(box.x + 4, box.y + 5)
            .fill(node.getStyle('shadow'));

        var shape = node.getStyle('shape');
        if(!shape){
            shadow.setSize(box.width, box.height)
            shadow.setRadius(node.getStyle('radius'));

        }else if(shape=='circle'){
            var width= Math.max(box.width,box.height);
            shadow.setSize(width, width)
            shadow.setRadius(width/2);
        }
    }
});


var marker = new kity.Marker();

marker.setWidth(10);
marker.setHeight(12);
marker.setRef(0, 0);
marker.setViewBox(-6, -4, 8, 10);

marker.addShape(new kity.Path().setPathData('M-5-3l5,3,-5,3').stroke('#33ffff'));

var wireframeOption = /wire/.test(window.location.href);
var WireframeRenderer = kity.createClass('WireframeRenderer', {
    base: Renderer,

    create: function() {
        var wireframe = new kity.Group();
        var oxy = this.oxy = new kity.Path()
            .stroke('#f6f')
            .setPathData('M0,-50L0,50M-50,0L50,0');

        var box = this.wireframe = new kity.Rect()
            .stroke('lightgreen');

        var vectorIn = this.vectorIn = new kity.Path()
            .stroke('#66ffff');
        var vectorOut = this.vectorOut = new kity.Path()
            .stroke('#66ffff');

        vectorIn.setMarker(marker, 'end');
        vectorOut.setMarker(marker, 'end');

        return wireframe.addShapes([oxy, box, vectorIn, vectorOut]);
    },

    shouldRender: function() {
        return wireframeOption;
    },

    update: function(created, node, box) {
        this.wireframe
            .setPosition(box.x, box.y)
            .setSize(box.width, box.height);
        var pin = node.getVertexIn();
        var pout = node.getVertexOut();
        var vin = node.getLayoutVectorIn().normalize(30);
        var vout = node.getLayoutVectorOut().normalize(30);
        this.vectorIn.setPathData(['M', pin.offset(vin.reverse()), 'L', pin]);
        this.vectorOut.setPathData(['M', pout, 'l', vout]);
    }
});

KityMinder.registerModule('OutlineModule', function() {
    return {
        events: (!wireframeOption ? null : {
            'ready': function() {
                this.getPaper().addResource(marker);
            },
            'layoutallfinish': function() {
                this.getRoot().traverse(function(node) {
                    node.getRenderer('WireframeRenderer').update(null, node, node.getContentBox());
                });
            }
        }),
        renderers: {
            outline: OutlineRenderer,
            outside: [ShadowRenderer, WireframeRenderer]
        }
    };
});
KityMinder.Geometry = (function() {
    var g = {};
    var min = Math.min,
        max = Math.max,
        abs = Math.abs;
    var own = Object.prototype.hasOwnProperty;

    g.isNumberInRange = function(number, range) {
        return number > range[0] && number < range[1];
    };

    g.getDistance = function(p1, p2) {
        return kity.Vector.fromPoints(p1, p2).length();
    };

    function wrapBox(box) {
        box.width = box.right - box.left;
        box.height = box.bottom - box.top;
        box.x = box.left;
        box.y = box.top;
        box.cx = box.x + box.width / 2;
        box.cy = box.y + box.height / 2;
        return box;
    }

    function uniformBox(box) {
        // duck check
        if ('x' in box) {
            box.left = box.x;
            box.right = box.x + box.width;
            box.top = box.y;
            box.bottom = box.y + box.height;
        }
    }

    g.wrapBox = wrapBox;

    g.getBox = function(p1, p2) {
        return wrapBox({
            left: min(p1.x, p2.x),
            right: max(p1.x, p2.x),
            top: min(p1.y, p2.y),
            bottom: max(p1.y, p2.y)
        });
    };

    g.mergeBox = function(b1, b2) {
        uniformBox(b1);
        uniformBox(b2);
        return wrapBox({
            left: min(b1.left, b2.left),
            right: max(b1.right, b2.right),
            top: min(b1.top, b2.top),
            bottom: max(b1.bottom, b2.bottom)
        });
    };

    g.getBoxRange = function(box) {
        return {
            x: [box.left, box.right],
            y: [box.top, box.bottom]
        };
    };

    g.getBoxVertex = function(box) {
        return {
            leftTop: {
                x: box.left,
                y: box.top
            },
            rightTop: {
                x: box.right,
                y: box.top
            },
            leftBottom: {
                x: box.left,
                y: box.bottom
            },
            rightBottom: {
                x: box.right,
                y: box.bottom
            }
        };
    };

    g.isPointInsideBox = function(p, b) {
        uniformBox(b);
        var ranges = g.getBoxRange(b);
        return g.isNumberInRange(p.x, ranges.x) && g.isNumberInRange(p.y, ranges.y);
    };

    g.getIntersectBox = function(b1, b2) {
        uniformBox(b1);
        uniformBox(b2);
        var minx = max(b1.left, b2.left),
            miny = max(b1.top, b2.top),
            maxx = min(b1.right, b2.right),
            maxy = min(b1.bottom, b2.bottom);
        return minx < maxx && miny < maxy ? wrapBox({
            left: minx,
            right: maxx,
            top: miny,
            bottom: maxy
        }) : null;
    };

    g.snapToSharp = function(unknown) {
        if (utils.isNumber(unknown)) {
            return (unknown | 0) + 0.5;
        }
        if (utils.isArray(unknown)) {
            return unknown.map(g.snapToSharp);
        }
        ['x', 'y', 'left', 'top', 'right', 'bottom'].forEach(function(n) {
            if (own.call(unknown, n)) {
                unknown[n] = g.snapToSharp(unknown[n]);
            }
        });
        return unknown;
    };

    g.expandBox = function(box, sizeX, sizeY) {
        if (sizeY === undefined) {
            sizeY = sizeX;
        }
        return wrapBox({
            left: box.left - sizeX,
            top: box.top - sizeY,
            right: box.right + sizeX,
            bottom: box.bottom + sizeY
        });
    };

    return g;
})();
KityMinder.registerModule("HistoryModule", function() {

    var km = this;

    var Scene = kity.createClass('Scene', {
        constructor: function(root,inputStatus) {
            this.data = root.clone();
            this.inputStatus = inputStatus;
        },
        getData: function() {
            return this.data;
        },
        cloneData: function() {
            return this.getData().clone();
        },
        equals: function(scene) {
            return this.getData().equals(scene.getData());

        },
        isInputStatus:function(){
            return this.inputStatus;
        },
        setInputStatus:function(status){
            this.inputStatus = status;
        }
    });
    var HistoryManager = kity.createClass('HistoryManager', {
        constructor: function(km) {
            this.list = [];
            this.index = 0;
            this.hasUndo = false;
            this.hasRedo = false;
            this.km = km;
        },
        undo: function() {
            if (this.hasUndo) {
                var currentScene = this.list[this.index];
                //如果是输入文字时的保存，直接回复当前场景
                if(currentScene && currentScene.isInputStatus()){
                    this.saveScene();
                    this.restore(--this.index);
                    currentScene.setInputStatus(false);
                    return;
                }
                if(this.list.length == 1){
                    this.restore(0);
                    return;
                }
                if (!this.list[this.index - 1] && this.list.length == 1) {
                    this.reset();
                    return;
                }
                while (this.list[this.index].equals(this.list[this.index - 1])) {
                    this.index--;
                    if (this.index === 0) {
                        return this.restore(0);
                    }
                }
                this.restore(--this.index);
            }
        },
        redo: function() {
            if (this.hasRedo) {
                while (this.list[this.index].equals(this.list[this.index + 1])) {
                    this.index++;
                    if (this.index == this.list.length - 1) {
                        return this.restore(this.index);
                    }
                }
                this.restore(++this.index);
            }
        },
        partialRenewal: function(target) {
            var selectedNodes = [];
            function compareNode(source, target) {
                if (source.getText() != target.getText()) {
                    return false;
                }
                if (utils.compareObject(source.getData(), target.getData()) === false) {
                    return false;
                }
                if (utils.compareObject(source.getTmpData(), target.getTmpData()) === false) {
                    return false;
                }
                return true;
            }

            function appendChildNode(parent, child) {
                if (child.isSelected()) {
                    selectedNodes.push(child);
                }
                km.appendNode(child, parent);
                child.render();

                var children = utils.cloneArr(child.children);
                for (var i = 0, ci; ci = children[i++];) {
                    appendChildNode(child, ci);
                }
            }

            function traverseNode(srcNode, tagNode) {

                if (compareNode(srcNode, tagNode) === false) {
                    srcNode.setValue(tagNode);
                }
                //todo，这里有性能问题，变成全部render了
                srcNode.render();
                if (srcNode.isSelected()) {
                    selectedNodes.push(srcNode);
                }
                for (var i = 0, j = 0, si, tj;
                    (si = srcNode.children[i], tj = tagNode.children[j], si || tj); i++, j++) {
                    if (si && !tj) {
                        i--;
                        km.removeNode(si);
                    } else if (!si && tj) {
                        j--;
                        appendChildNode(srcNode, tj);
                    } else {
                        traverseNode(si, tj);
                    }
                }
            }

            traverseNode(km.getRoot(), target);
            km.layout(200);

            km.select(selectedNodes,true);

            selectedNodes = [];

        },
        restore: function(index) {
            index = index === undefined ? this.index : index;
            var scene = this.list[index];
            this.partialRenewal(scene.cloneData());
            this.update();
            this.km.fire('restoreScene');
            this.km.fire('contentChange');
        },
        getScene: function(inputStatus) {
            return new Scene(this.km.getRoot(),inputStatus);
        },
        saveScene: function(inputStatus) {
            var currentScene = this.getScene(inputStatus);
            var lastScene = this.list[this.index];
            if (lastScene && lastScene.equals(currentScene)) {
                if(inputStatus){
                    lastScene.setInputStatus(true);
                    this.update();
                }
                return;
            }
            this.list = this.list.slice(0, this.index + 1);
            this.list.push(currentScene);
            //如果大于最大数量了，就把最前的剔除
            if (this.list.length > this.km.getOptions('maxUndoCount')) {
                this.list.shift();
            }
            this.index = this.list.length - 1;
            //跟新undo/redo状态
            this.update();
        },
        update: function() {

            this.hasRedo = !!this.list[this.index + 1];
            this.hasUndo = !!this.list[this.index - 1];
            var currentScene = this.list[this.index];
            if(currentScene && currentScene.isInputStatus()){
                this.hasUndo = true;
            }

        },
        reset: function() {
            this.list = [];
            this.index = 0;
            this.hasUndo = false;
            this.hasRedo = false;
        }
    });
    //为km实例添加history管理
    this.historyManager = new HistoryManager(this);


    return {
        defaultOptions: {
            maxUndoCount: 20,
            maxInputCount: 20
        },
        "commands": {
            "undo": kity.createClass("UndoCommand", {
                base: Command,

                execute: function(km) {
                    km.historyManager.undo();
                },

                queryState: function(km) {
                    return km.historyManager.hasUndo ? 0 : -1;
                },

                isNeedUndo: function() {
                    return false;
                }
            }),
            "redo": kity.createClass("RedoCommand", {
                base: Command,

                execute: function(km) {
                    km.historyManager.redo();
                },

                queryState: function(km) {
                    return km.historyManager.hasRedo ? 0 : -1;
                },
                isNeedUndo: function() {
                    return false;
                }
            })
        },
        commandShortcutKeys: {
            "undo": "ctrl+z", //undo
            "redo": "ctrl+y" //redo
        },
        "events": {
            "saveScene": function(e) {
                this.historyManager.saveScene(e.inputStatus);
            },
            "import": function() {
                this.historyManager.reset();
            }
        }
    };
});
KityMinder.registerModule('ProgressModule', function() {
    var minder = this;

    var PROGRESS_DATA = 'progress';

    // Designed by Akikonata
    var BG_COLOR = '#FFED83';
    var PIE_COLOR = '#43BC00';
    var SHADOW_PATH = 'M10,3c4.418,0,8,3.582,8,8h1c0-5.523-3.477-10-9-10S1,5.477,1,11h1C2,6.582,5.582,3,10,3z';
    var SHADOW_COLOR = '#8E8E8E';
    var FRAME_PATH = 'M10,0C4.477,0,0,4.477,0,10c0,5.523,4.477,10,10,10s10-4.477,10-10C20,4.477,15.523,0,10,0zM10,18c-4.418,0-8-3.582-8-8s3.582-8,8-8s8,3.582,8,8S14.418,18,10,18z';
    var FRAME_GRAD = new kity.LinearGradient().pipe(function(g) {
        g.setStartPosition(0, 0);
        g.setEndPosition(0, 1);
        g.addStop(0, '#fff');
        g.addStop(1, '#ccc');
    });
    var CHECK_PATH = 'M15.812,7.896l-6.75,6.75l-4.5-4.5L6.25,8.459l2.812,2.803l5.062-5.053L15.812,7.896z';
    var CHECK_COLOR = '#EEE';

    minder.getPaper().addResource(FRAME_GRAD);

    // 进度图标的图形
    var ProgressIcon = kity.createClass('ProgressIcon', {
        base: kity.Group,

        constructor: function(value) {
            this.callBase();
            this.setSize(20);
            this.create();
            this.setValue(value);
            this.setId(KityMinder.uuid('node_progress'));
            this.translate(0.5, 0.5);
        },

        setSize: function(size) {
            this.width = this.height = size;
        },

        create: function() {

            var bg, pie, shadow, frame, check;

            bg = new kity.Circle(9)
                .fill(BG_COLOR);

            pie = new kity.Pie(9, 0)
                .fill(PIE_COLOR);

            shadow = new kity.Path()
                .setPathData(SHADOW_PATH)
                .setTranslate(-10, -10)
                .fill(SHADOW_COLOR);

            frame = new kity.Path()
                .setTranslate(-10, -10)
                .setPathData(FRAME_PATH)
                .fill(FRAME_GRAD);

            check = new kity.Path()
                .setTranslate(-10, -10)
                .setPathData(CHECK_PATH)
                .fill(CHECK_COLOR);

            this.addShapes([bg, pie, shadow, check, frame]);
            this.pie = pie;
            this.check = check;
        },

        setValue: function(value) {
            this.pie.setAngle(-360 * (value - 1) / 8);
            this.check.setVisible(value == 9);
        }
    });

    var ProgressCommand = kity.createClass('ProgressCommand', {
        base: Command,
        execute: function(km, value) {
            var nodes = km.getSelectedNodes();
            for (var i = 0; i < nodes.length; i++) {
                nodes[i].setData(PROGRESS_DATA, value || null).render();
            }
            km.layout();
        },
        queryValue: function(km) {
            var nodes = km.getSelectedNodes();
            var val;
            for (var i = 0; i < nodes.length; i++) {
                val = nodes[i].getData(PROGRESS_DATA);
                if (val) break;
            }
            return val|| null;
        },

        queryState: function(km) {
            return km.getSelectedNodes().length ? 0 : -1;
        }
    });

    return {
        'commands': {
            'progress': ProgressCommand
        },
        'renderers': {
            left: kity.createClass('ProgressRenderer', {
                base: KityMinder.Renderer,

                create: function(node) {
                    return new ProgressIcon();
                },

                shouldRender: function(node) {
                    return node.getData(PROGRESS_DATA);
                },

                update: function(icon, node, box) {
                    var data = node.getData(PROGRESS_DATA);
                    var spaceLeft = node.getStyle('space-left');
                    var x, y;

                    icon.setValue(data);

                    x = box.left - icon.width - spaceLeft;
                    y = -icon.height / 2;
                    icon.setTranslate(x + icon.width / 2, y + icon.height / 2);

                    return new kity.Box(x, y, icon.width, icon.height);
                }
            })
        }
    };
});
KityMinder.registerModule('PriorityModule', function() {
    var minder = this;

    // Designed by Akikonata
    // [MASK, BACK]
    var PRIORITY_COLORS = [null, 
        ['#FF1200', '#840023'], // 1 - red
        ['#0074FF', '#01467F'], // 2 - blue
        ['#00AF00', '#006300'], // 3 - green
        ['#FF962E', '#B25000'], // 4 - orange
        ['#A464FF', '#4720C4'], // 5 - purple
        ['#A3A3A3', '#515151'], // 6,7,8,9 - gray
        ['#A3A3A3', '#515151'],
        ['#A3A3A3', '#515151'],
        ['#A3A3A3', '#515151'],
    ]; // hue from 1 to 5

    var BACK_PATH = 'M0,13c0,3.866,3.134,7,7,7h6c3.866,0,7-3.134,7-7V7H0V13z';
    var MASK_PATH = 'M20,10c0,3.866-3.134,7-7,7H7c-3.866,0-7-3.134-7-7V7c0-3.866,3.134-7,7-7h6c3.866,0,7,3.134,7,7V10z';

    var PRIORITY_DATA = 'priority';

    // 进度图标的图形
    var PriorityIcon = kity.createClass('PriorityIcon', {
        base: kity.Group,

        constructor: function() {
            this.callBase();
            this.setSize(20);
            this.create();
            this.setId(KityMinder.uuid('node_priority'));
        },

        setSize: function(size) {
            this.width = this.height = size;
        },

        create: function() {
            var white, back, mask, number; // 4 layer

            white = new kity.Path().setPathData(MASK_PATH).fill('white');
            back = new kity.Path().setPathData(BACK_PATH).setTranslate(0.5, 0.5);
            mask = new kity.Path().setPathData(MASK_PATH).setOpacity(0.8).setTranslate(0.5, 0.5);

            number = new kity.Text()
                .setX(this.width / 2 - 0.5).setY(this.height / 2 - 1.5)
                .setTextAnchor('middle')
                .setVerticalAlign('middle')
                .setFontItalic(true)
                .setFontSize(14)
                .fill('white');

            this.addShapes([back, mask, number]);
            this.mask = mask;
            this.back = back;
            this.number = number;
        },

        setValue: function(value) {
            var back = this.back,
                mask = this.mask,
                number = this.number;

            var color = PRIORITY_COLORS[value];

            if (color) {
                back.fill(color[1]);
                mask.fill(color[0]);
            }

            number.setContent(value);
        }
    });

    // 提供的命令
    var PriorityCommand = kity.createClass('SetPriorityCommand', {
        base: Command,
        execute: function(km, value) {
            var nodes = km.getSelectedNodes();
            for (var i = 0; i < nodes.length; i++) {
                nodes[i].setData(PRIORITY_DATA, value || null).render();
            }
            km.layout();
        },
        queryValue: function(km) {
            var nodes = km.getSelectedNodes();
            var val;
            for (var i = 0; i < nodes.length; i++) {
                val = nodes[i].getData(PRIORITY_DATA);
                if (val) break;
            }
            return val || null;
        },

        queryState: function(km) {
            return km.getSelectedNodes().length ? 0 : -1;
        }
    });
    return {
        'commands': {
            'priority': PriorityCommand,
        },
        'renderers': {
            left: kity.createClass('PriorityRenderer', {
                base: KityMinder.Renderer,

                create: function(node) {
                    return new PriorityIcon();
                },

                shouldRender: function(node) {
                    return node.getData(PRIORITY_DATA);
                },

                update: function(icon, node, box) {
                    var data = node.getData(PRIORITY_DATA);
                    var spaceLeft = node.getStyle('space-left'),
                        x, y;

                    icon.setValue(data);
                    x = box.left - icon.width - spaceLeft;
                    y = -icon.height / 2;

                    icon.setTranslate(x, y);

                    return new kity.Box({
                        x: x,
                        y: y,
                        width: icon.width,
                        height: icon.height
                    });
                }
            })
        }
    };
});
KityMinder.registerModule('image', function() {
    function loadImageSize(url, callback) {
        var img = document.createElement('img');
        img.onload = function() {
            callback(img.width, img.height);
        };
        img.onerror = function() {
            callback(null);
        };
        img.src = url;
    }

    function fitImageSize(width, height, maxWidth, maxHeight) {
        var ratio = width / height,
            fitRatio = maxWidth / maxHeight;

        // 宽高比大于最大尺寸的宽高比，以宽度为标准适应
        if (width > maxWidth && ratio > fitRatio) {
            width = maxWidth;
            height = width / ratio;
        } else if (height > maxHeight) {
            height = maxHeight;
            width = height * ratio;
        }

        return {
            width: width | 0,
            height: height | 0
        };
    }

    var ImageCommand = kity.createClass('ImageCommand', {
        base: Command,

        execute: function(km, url, title) {
            var nodes = km.getSelectedNodes();

            loadImageSize(url, function(width, height) {
                if (!width) return;
                utils.each(nodes, function(i, n) {
                    var size = fitImageSize(
                        width, height,
                        km.getOptions('maxImageWidth'),
                        km.getOptions('maxImageHeight'));
                    n.setData('image', url);
                    n.setData('imageTitle', title);
                    n.setData('imageSize', size);
                    n.render();
                });
                km.fire("saveScene");
                km.layout(300);
            });

        },
        queryState: function(km) {
            var nodes = km.getSelectedNodes(),
                result = 0;
            if (nodes.length === 0) {
                return -1;
            }
            utils.each(nodes, function(i, n) {
                if (n && n.getData('image')) {
                    result = 0;
                    return false;
                }
            });
            return result;
        },
        queryValue: function(km) {
            var node = km.getSelectedNode();
            return {
                url: node.getData('image'),
                title: node.getData('imageTitle')
            };
        }
    });

    var RemoveImageCommand = kity.createClass('RemoveImageCommand', {
        base: Command,

        execute: function(km) {
            var nodes = km.getSelectedNodes();
            utils.each(nodes, function(i, n) {
                n.setData('image').render();
            });
            km.layout(300);
        },
        queryState: function(km) {
            var nodes = km.getSelectedNodes();

            if (nodes.length === 0) {
                return -1;
            }
            var image = false;
            utils.each(nodes, function(i, n) {
                if (n.getData('image')) {
                    image = true;
                    return false;
                }
            });
            if (image) {
                return 0;
            }
            return -1;
        }
    });

    var ImageRenderer = kity.createClass('ImageRenderer', {
        base: KityMinder.Renderer,

        create: function(node) {
            return new kity.Image(node.getData('image'));
        },

        shouldRender: function(node) {
            return node.getData('image');
        },

        update: function(image, node, box) {
            var url = node.getData('image');
            var title = node.getData('imageTitle');
            var size = node.getData('imageSize');
            var spaceTop = node.getStyle('space-top');

            if (!size) return;

            if (title) {
                image.node.setAttributeNS('http://www.w3.org/1999/xlink', 'title', title);
            }

            var x = box.cx - size.width / 2;
            var y = box.y - size.height - spaceTop;

            image
                .setUrl(url)
                .setX(x | 0)
                .setY(y | 0)
                .setWidth(size.width | 0)
                .setHeight(size.height | 0);

            return new kity.Box(x | 0, y | 0, size.width | 0, size.height | 0);
        }
    });

    return {
        'defaultOptions': {
            'maxImageWidth': 200,
            'maxImageHeight': 200
        },
        'commands': {
            'image': ImageCommand,
            'removeimage': RemoveImageCommand
        },
        'renderers': {
            'top': ImageRenderer
        }
    };
});
KityMinder.registerModule('Resource', function() {

    // String Hash
    // https://github.com/drostie/sha3-js/edit/master/blake32.min.js
    var blake32=(function(){var k,g,r,l,m,o,p,q,t,w,x;x=4*(1<<30);k=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19];m=[0x243F6A88,0x85A308D3,0x13198A2E,0x03707344,0xA4093822,0x299F31D0,0x082EFA98,0xEC4E6C89,0x452821E6,0x38D01377,0xBE5466CF,0x34E90C6C,0xC0AC29B7,0xC97C50DD,0x3F84D5B5,0xB5470917];w=function(i){if(i<0){i+=x}return("00000000"+i.toString(16)).slice(-8)};o=[[16,50,84,118,152,186,220,254],[174,132,249,109,193,32,123,53],[139,12,37,223,234,99,23,73],[151,19,205,235,98,165,4,143],[9,117,66,250,30,203,134,211],[194,166,176,56,212,87,239,145],[92,241,222,164,112,54,41,184],[189,231,28,147,5,79,104,162],[246,158,59,128,44,125,65,90],[42,72,103,81,191,233,195,13]];p=function(a,b,n){var s=q[a]^q[b];q[a]=(s>>>n)|(s<<(32-n))};g=function(i,a,b,c,d){var u=l+o[r][i]%16,v=l+(o[r][i]>>4);a%=4;b=4+b%4;c=8+c%4;d=12+d%4;q[a]+=q[b]+(t[u]^m[v%16]);p(d,a,16);q[c]+=q[d];p(b,c,12);q[a]+=q[b]+(t[v]^m[u%16]);p(d,a,8);q[c]+=q[d];p(b,c,7)};return function(a,b){if(!(b instanceof Array&&b.length===4)){b=[0,0,0,0]}var c,d,e,L,f,h,j,i;d=k.slice(0);c=m.slice(0,8);for(r=0;r<4;r+=1){c[r]^=b[r]}e=a.length*16;f=(e%512>446||e%512===0)?0:e;if(e%512===432){a+="\u8001"}else{a+="\u8000";while(a.length%32!==27){a+="\u0000"}a+="\u0001"}t=[];for(i=0;i<a.length;i+=2){t.push(a.charCodeAt(i)*65536+a.charCodeAt(i+1))}t.push(0);t.push(e);h=t.length-16;j=0;for(l=0;l<t.length;l+=16){j+=512;L=(l===h)?f:Math.min(e,j);q=d.concat(c);q[12]^=L;q[13]^=L;for(r=0;r<10;r+=1){for(i=0;i<8;i+=1){if(i<4){g(i,i,i,i,i)}else{g(i,i,i+1,i+2,i+3)}}}for(i=0;i<8;i+=1){d[i]^=b[i%4]^q[i]^q[i+8]}}return d.map(w).join("")}}());

    /**
     * 自动使用的颜色序列
     */
    var RESOURCE_COLOR_SERIES = [51, 303, 75, 200, 157, 0, 26, 254].map(function(h) {
        return kity.Color.createHSL(h, 100, 85);
    });

    /**
     * 在 Minder 上拓展一些关于资源的支持接口
     */
    kity.extendClass(Minder, {
        
        /**
         * 获取字符串的哈希值
		 *
         * @param {String} str
         * @return {Number} hashCode
         */
        getHashCode: function(str) {
            str = blake32(str);
            var hash = 1315423911, i, ch;
            for (i = str.length - 1; i >= 0; i--) {
                ch = str.charCodeAt(i);
                hash ^= ((hash << 5) + ch + (hash >> 2));
            }
            return  (hash & 0x7FFFFFFF);
        },

        /**
         * 获取脑图中某个资源对应的颜色
         *
         * 如果存在同名资源，则返回已经分配给该资源的颜色，否则分配给该资源一个颜色，并且返回
         *
         * 如果资源数超过颜色序列数量，返回哈希颜色
         *
         * @param {String} resource 资源名称
         * @return {Color}
         */
        getResourceColor: function(resource) {
            var colorMapping = this._getResourceColorIndexMapping();
            var nextIndex;

            if (!colorMapping.hasOwnProperty(resource)) {
                // 找不到找下个可用索引
                nextIndex = this._getNextResourceColorIndex();
                colorMapping[resource] = nextIndex;
            }

            // 资源过多，找不到可用索引颜色，统一返回哈希函数得到的颜色
            return RESOURCE_COLOR_SERIES[colorMapping[resource]] || kity.Color.createHSL(Math.floor(this.getHashCode(resource) / 0x7FFFFFFF * 359), 100, 85);
        },

        /**
         * 获得已使用的资源的列表
         *
         * @return {Array}
         */
        getUsedResource: function() {
            var mapping = this._getResourceColorIndexMapping();
            var used = [],
                resource;

            for (resource in mapping) {
                if (mapping.hasOwnProperty(resource)) {
                    used.push(resource);
                }
            }

            return used;
        },

        /**
         * 获取脑图下一个可用的资源颜色索引
         *
         * @return {int}
         */
        _getNextResourceColorIndex: function() {
            // 获取现有颜色映射
            //     resource => color_index
            var colorMapping = this._getResourceColorIndexMapping();

            var resource, used, i;

            used = [];

            // 抽取已经使用的值到 used 数组
            for (resource in colorMapping) {
                if (colorMapping.hasOwnProperty(resource)) {
                    used.push(colorMapping[resource]);
                }
            }

            // 枚举所有的可用值，如果还没被使用，返回
            for (i = 0; i < RESOURCE_COLOR_SERIES.length; i++) {
                if (!~used.indexOf(i)) return i;
            }

            // 没有可用的颜色了
            return -1;
        },

        // 获取现有颜色映射
        //     resource => color_index
        _getResourceColorIndexMapping: function() {
            return this._resourceColorMapping || (this._resourceColorMapping = {});
        }

    });


    /**
     * @class 设置资源的命令
     *
     * @example
     *
     * // 设置选中节点资源为 "张三"
     * minder.execCommand('resource', ['张三']);
     *
     * // 添加资源 "李四" 到选中节点
     * var resource = minder.queryCommandValue();
     * resource.push('李四');
     * minder.execCommand('resource', resource);
     *
     * // 清除选中节点的资源
     * minder.execCommand('resource', null);
     */
    var ResourceCommand = kity.createClass('ResourceCommand', {

        base: Command,

        execute: function(minder, resource) {
            var nodes = minder.getSelectedNodes();

            if (typeof(resource) == 'string') {
                resource = [resource];
            }

            nodes.forEach(function(node) {
                node.setData('resource', resource).render();
            });

            minder.layout(200);
        },

        queryValue: function(minder) {
            var nodes = minder.getSelectedNodes();
            var resource = [];

            nodes.forEach(function(node) {
                var nodeResource = node.getData('resource');

                if (!nodeResource) return;

                nodeResource.forEach(function(name) {
                    if (!~resource.indexOf(name)) {
                        resource.push(name);
                    }
                });
            });

            return resource;
        },

        queryState: function(km) {
            return km.getSelectedNode() ? 0 : -1;
        }
    });

    /**
     * @class 资源的覆盖图形
     *
     * 该类为一个资源以指定的颜色渲染一个动态的覆盖图形
     */
    var ResourceOverlay = kity.createClass('ResourceOverlay', {
        base: kity.Group,

        constructor: function() {
            this.callBase();

            var text, rect;

            rect = this.rect = new kity.Rect().setRadius(4);

            text = this.text = new kity.Text()
                .setFontSize(12)
                .setVerticalAlign('middle');

            this.addShapes([rect, text]);
        },

        setValue: function(resourceName, color) {
            var paddingX = 8,
                paddingY = 4,
                borderRadius = 4;
            var text, box, rect;

            text = this.text;

            if (resourceName == this.lastResourceName) {

                box = this.lastBox;

            } else {

                text.setContent(resourceName);

                box = text.getBoundaryBox();
                this.lastResourceName = resourceName;
                this.lastBox = box;

            }

            text.setX(paddingX).fill(color.dec('l', 70));

            rect = this.rect;
            rect.setPosition(0, box.y - paddingY);
            this.width = Math.round(box.width + paddingX * 2);
            this.height = Math.round(box.height + paddingY * 2);
            rect.setSize(this.width, this.height);
            rect.fill(color);
        }
    });

    /**
     * @class 资源渲染器
     */
    var ResourceRenderer = kity.createClass('ResourceRenderer', {
        base: KityMinder.Renderer,

        create: function(node) {
            this.overlays = [];
            return new kity.Group();
        },

        shouldRender: function(node) {
            return node.getData('resource') && node.getData('resource').length;
        },

        update: function(container, node, box) {
            var spaceRight = node.getStyle('space-right');

            var overlays = this.overlays;
            var resource = node.getData('resource');
            var minder = node.getMinder();
            var i, overlay, x;

            x = 0;
            for (i = 0; i < resource.length; i++) {
                x += spaceRight;

                overlay = overlays[i];
                if (!overlay) {
                    overlay = new ResourceOverlay();
                    overlays.push(overlay);
                    container.addShape(overlay);
                }
                overlay.setVisible(true);
                overlay.setValue(resource[i], minder.getResourceColor(resource[i]));
                overlay.setTranslate(x, -1);

                x += overlay.width;
            }

            while ((overlay = overlays[i++])) overlay.setVisible(false);

            container.setTranslate(box.right, 0);

            return new kity.Box({
                x: box.right,
                y: Math.round(-overlays[0].height / 2),
                width: x,
                height: overlays[0].height
            });
        }
    });

    return {
        commands: {
            'resource': ResourceCommand
        },

        renderers: {
            right: ResourceRenderer
        }
    };
});

/**
 * @fileOverview
 *
 * 支持节点详细信息（HTML）格式
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerModule('NoteModule', function() {

    var NOTE_PATH = 'M9,9H3V8h6L9,9L9,9z M9,7H3V6h6V7z M9,5H3V4h6V5z M8.5,11H2V2h8v7.5 M9,12l2-2V1H1v11';

    var NoteCommand = kity.createClass('NoteCommand', {
        base: Command,

        execute: function(minder, note) {
            var node = minder.getSelectedNode();
            node.setData('note', note);
            node.render();
            node.getMinder().layout(300);
        },

        queryState: function(minder) {
            return minder.getSelectedNodes().length === 1 ? 0 : -1;
        },

        queryValue: function(minder) {
            var node = minder.getSelectedNode();
            return node && node.getData('note');
        }
    });

    var NoteIcon = kity.createClass('NoteIcon', {
        base: kity.Group,

        constructor: function() {
            this.callBase();
            this.width = 16;
            this.height = 17;
            this.rect = new kity.Rect(16, 17, 0.5, -8.5, 2).fill('transparent');
            this.path = new kity.Path().setPathData(NOTE_PATH).setTranslate(2.5, -6.5);
            this.addShapes([this.rect, this.path]);

            this.on('mouseover', function() {
                this.rect.fill('rgba(255, 255, 200, .8)');
            }).on('mouseout', function() {
                this.rect.fill('transparent');
            });

            this.setStyle('cursor', 'pointer');
        }
    });

    var NoteIconRenderer = kity.createClass('NoteIconRenderer', {
        base: KityMinder.Renderer,

        create: function(node) {
            var icon = new NoteIcon();
            icon.on('mousedown', function(e) {
                e.preventDefault();
                node.getMinder().fire('editnoterequest');
            });
            icon.on('mouseover', function() {
                node.getMinder().fire('shownoterequest', {node: node, icon: icon});
            });
            icon.on('mouseout', function() {
                node.getMinder().fire('hidenoterequest', {node: node, icon: icon});
            });
            return icon;
        },

        shouldRender: function(node) {
            return node.getData('note');
        },

        update: function(icon, node, box) {
            var x = box.right + node.getStyle('space-left');
            var y = box.cy;

            icon.path.fill(node.getStyle('color'));
            icon.setTranslate(x, y);

            return new kity.Box(x, Math.round(y - icon.height / 2), icon.width, icon.height);
        }

    });

    return {
        renderers: {
            right: NoteIconRenderer
        },
        commands: {
            'note': NoteCommand
        }
    };
});
var ViewDragger = kity.createClass("ViewDragger", {
    constructor: function(minder) {
        this._minder = minder;
        this._enabled = false;
        this._bind();
        var me = this;
        this._minder.getViewDragger = function() {
            return me;
        };
    },

    isEnabled: function() {
        return this._enabled;
    },

    setEnabled: function(value) {
        var paper = this._minder.getPaper();
        paper.setStyle('cursor', value ? 'pointer' : 'default');
        paper.setStyle('cursor', value ? '-webkit-grab' : 'default');
        this._enabled = value;
    },

    move: function(offset, duration) {
        var minder = this._minder;

        var targetPosition = this.getMovement().offset(offset);

        this.moveTo(targetPosition, duration);
    },

    moveTo: function(position, duration) {

        if (duration) {
            var dragger = this;

            if (this._moveTimeline) this._moveTimeline.stop();

            this._moveTimeline = this._minder.getRenderContainer().animate(new kity.Animator(
                this.getMovement(),
                position,
                function(target, value) {
                    dragger.moveTo(value);
                }
            ), duration, 'easeOutCubic');

            this._moveTimeline.on('finish', function() {
                dragger._moveTimeline = null;
            });

            return this;
        }

        this._minder.getRenderContainer().setTranslate(position.round());
        this._minder.fire('viewchange');
    },

    getMovement: function() {
        var translate = this._minder.getRenderContainer().transform.translate;
        return translate ? translate[0] : new kity.Point();
    },

    getView: function() {
        var minder = this._minder;
        var c = {
            width: minder.getRenderTarget().clientWidth,
            height: minder.getRenderTarget().clientHeight
        };
        var m = this.getMovement();
        var box = new kity.Box(0, 0, c.width, c.height);
        var viewMatrix = minder.getPaper().getViewPortMatrix();
        return viewMatrix.inverse().translate(-m.x, -m.y).transformBox(box);
    },

    _bind: function() {
        var dragger = this,
            isTempDrag = false,
            lastPosition = null,
            currentPosition = null;

        function dragEnd(e) {
            if (!lastPosition) return;
            
            lastPosition = null;

            e.stopPropagation();

            // 临时拖动需要还原状态
            if (isTempDrag) {
                dragger.setEnabled(false);
                isTempDrag = false;
                if (dragger._minder.getStatus() == 'hand')
                    dragger._minder.rollbackStatus();
            }
            var paper = dragger._minder.getPaper();
            paper.setStyle('cursor', dragger._minder.getStatus() == 'hand' ? '-webkit-grab' : 'default');
        }

        this._minder.on('normal.mousedown normal.touchstart ' +
            'inputready.mousedown inputready.touchstart ' +
            'readonly.mousedown readonly.touchstart', function(e) {
                if (e.originEvent.button == 2) {
                    e.originEvent.preventDefault(); // 阻止中键拉动
                }
                // 点击未选中的根节点临时开启
                if (e.getTargetNode() == this.getRoot() || e.originEvent.button == 2 || e.originEvent.altKey) {
                    lastPosition = e.getPosition();
                    isTempDrag = true;
                }
            })

        .on('normal.mousemove normal.touchmove ' +
            'readonly.mousemove readonly.touchmove ' +
            'inputready.mousemove inputready.touchmove', function(e) {
                if (e.type == 'touchmove') {
                    e.preventDefault(); // 阻止浏览器的后退事件
                }
                if (!isTempDrag) return;
                var offset = kity.Vector.fromPoints(lastPosition, e.getPosition());
                if (offset.length() > 10) {
                    this.setStatus('hand', true);
                    var paper = dragger._minder.getPaper();
                    paper.setStyle('cursor', '-webkit-grabbing');
                }
            })

        .on('hand.beforemousedown hand.beforetouchstart', function(e) {
            // 已经被用户打开拖放模式
            if (dragger.isEnabled()) {
                lastPosition = e.getPosition();
                e.stopPropagation();
                var paper = dragger._minder.getPaper();
                paper.setStyle('cursor', '-webkit-grabbing');
            }
        })

        .on('hand.beforemousemove hand.beforetouchmove', function(e) {
            if (lastPosition) {
                currentPosition = e.getPosition();

                // 当前偏移加上历史偏移
                var offset = kity.Vector.fromPoints(lastPosition, currentPosition);
                dragger.move(offset);
                e.stopPropagation();
                e.preventDefault();
                e.originEvent.preventDefault();
                lastPosition = currentPosition;
            }
        })

        .on('mouseup touchend', dragEnd);

        window.addEventListener('mouseup', dragEnd);
        this._minder.on('contextmenu', function(e) {
            e.preventDefault();
        });
    }
});

KityMinder.registerModule('View', function() {

    var km = this;

    var ToggleHandCommand = kity.createClass('ToggleHandCommand', {
        base: Command,
        execute: function(minder) {

            if (minder.getStatus() != 'hand') {
                minder.setStatus('hand', true);
            } else {
                minder.rollbackStatus();
            }
            this.setContentChanged(false);

        },
        queryState: function(minder) {
            return minder.getStatus() == 'hand' ? 1 : 0;
        },
        enableReadOnly: true
    });

    var CameraCommand = kity.createClass('CameraCommand', {
        base: Command,
        execute: function(km, focusNode, duration) {

            focusNode = focusNode || km.getRoot();
            var viewport = km.getPaper().getViewPort();
            var offset = focusNode.getRenderContainer().getRenderBox('view');
            var dx = viewport.center.x - offset.x - offset.width / 2,
                dy = viewport.center.y - offset.y;
            var dragger = km._viewDragger;

            dragger.move(new kity.Point(dx, dy), duration);
            this.setContentChanged(false);
        },
        enableReadOnly: true
    });

    var MoveCommand = kity.createClass('MoveCommand', {
        base: Command,

        execute: function(km, dir, duration) {
            var dragger = km._viewDragger;
            var size = km._lastClientSize;
            switch (dir) {
                case 'up':
                    dragger.move(new kity.Point(0, size.height / 2), duration);
                    break;
                case 'down':
                    dragger.move(new kity.Point(0, -size.height / 2), duration);
                    break;
                case 'left':
                    dragger.move(new kity.Point(size.width / 2, 0), duration);
                    break;
                case 'right':
                    dragger.move(new kity.Point(-size.width / 2, 0), duration);
                    break;
            }
        },

        enableReadOnly: true
    });

    return {
        init: function() {
            this._viewDragger = new ViewDragger(this);
        },
        commands: {
            'hand': ToggleHandCommand,
            'camera': CameraCommand,
            'move': MoveCommand
        },
        events: {
            keydown: function(e) {
                var minder = this;

                ['up', 'down', 'left', 'right'].forEach(function(name) {
                    if (e.isShortcutKey('ctrl+' + name) && minder.getStatus() != 'textedit') {
                        minder.removeAllSelectedNodes();
                        minder.execCommand('move', name, 100);
                        e.preventDefault();
                    }
                });
                if (e.isShortcutKey('ctrl+enter')) {
                    minder.execCommand('camera', minder.getRoot(), 100);
                }
            },
            statuschange: function(e) {
                this._viewDragger.setEnabled(e.currentStatus == 'hand');
            },
            mousewheel: function(e) {
                var dx, dy;
                e = e.originEvent;
                if (e.ctrlKey || e.shiftKey) return;
                if ('wheelDeltaX' in e) {

                    dx = e.wheelDeltaX || 0;
                    dy = e.wheelDeltaY || 0;

                } else {

                    dx = 0;
                    dy = e.wheelDelta;

                }

                this._viewDragger.move({
                    x: dx / 2.5,
                    y: dy / 2.5
                });

                e.preventDefault();
            },
            'normal.dblclick readonly.dblclick': function(e) {
                if (e.kityEvent.targetShape instanceof kity.Paper) {
                    this.execCommand('camera', this.getRoot(), 800);
                }
            },
            ready: function() {
                this.execCommand('camera', null, 0);
                this._lastClientSize = {
                    width: this.getRenderTarget().clientWidth,
                    height: this.getRenderTarget().clientHeight
                };
            },
            resize: function(e) {
                var a = {
                        width: this.getRenderTarget().clientWidth,
                        height: this.getRenderTarget().clientHeight
                    },
                    b = this._lastClientSize;
                this._viewDragger.move(
                    new kity.Point((a.width - b.width) / 2 | 0, (a.height - b.height) / 2 | 0));
                this._lastClientSize = a;
            },
            'selectionchange layoutallfinish': function(e) {
                var selected = this.getSelectedNode();

                if (!selected) return;

                var dragger = this._viewDragger;
                var view = dragger.getView();
                var focus = selected.getLayoutBox();
                var space = 50;
                var dx = 0, dy = 0;

                if (focus.right > view.right) {
                    dx += view.right - focus.right - space;
                }
                else if (focus.left < view.left) {
                    dx += view.left - focus.left + space;
                }

                if (focus.bottom > view.bottom) {
                    dy += view.bottom - focus.bottom - space;
                }
                if (focus.top < view.top) {
                    dy += view.top - focus.top + space;
                }
                
                if (dx || dy) dragger.move(new kity.Point(dx, dy), 100);
            }
        }
    };
});
var GM = KityMinder.Geometry;

// 矩形的变形动画定义

var MoveToParentCommand = kity.createClass('MoveToParentCommand', {
    base: Command,
    execute: function(minder, nodes, parent) {
        var node;
        for (var i = nodes.length - 1; i >= 0; i--) {
            node = nodes[i];
            if (node.parent) {
                node.parent.removeChild(node);
                parent.appendChild(node);
                node.render();
            }
        }
        parent.expand();
        minder.select(nodes, true);
    }
});

var DropHinter = kity.createClass('DropHinter', {
    base: kity.Group,

    constructor: function() {
        this.callBase();
        this.rect = new kity.Rect();
        this.addShape(this.rect);
    },

    render: function(target) {
        this.setVisible(!!target);
        if (target) {
            this.rect
                .setBox(target.getLayoutBox())
                .setRadius(target.getStyle('radius') || 0)
                .stroke(
                    target.getStyle('drop-hint-color') || 'yellow',
                    target.getStyle('drop-hint-width') || 2
            );
            this.bringTop();
        }
    }
});

var OrderHinter = kity.createClass('OrderHinter', {
    base: kity.Group,

    constructor: function() {
        this.callBase();
        this.area = new kity.Rect();
        this.path = new kity.Path();
        this.addShapes([this.area, this.path]);
    },

    render: function(hint) {
        this.setVisible(!!hint);
        if (hint) {
            this.area.setBox(hint.area);
            this.area.fill(hint.node.getStyle('order-hint-area-color') || 'rgba(0, 255, 0, .5)');
            this.path.setPathData(hint.path);
            this.path.stroke(
                hint.node.getStyle('order-hint-path-color') || '#0f0',
                hint.node.getStyle('order-hint-path-width') || 1);
        }
    }
});

// 对拖动对象的一个替代盒子，控制整个拖放的逻辑，包括：
//    1. 从节点列表计算出拖动部分
//    2. 计算可以 drop 的节点，产生 drop 交互提示
var TreeDragger = kity.createClass('TreeDragger', {

    constructor: function(minder) {
        this._minder = minder;
        this._dropHinter = new DropHinter();
        this._orderHinter = new OrderHinter();
        minder.getRenderContainer().addShapes([this._dropHinter, this._orderHinter]);
    },

    dragStart: function(position) {
        // 只记录开始位置，不马上开启拖放模式
        // 这个位置同时是拖放范围收缩时的焦点位置（中心）
        this._startPosition = position;
    },

    dragMove: function(position) {
        // 启动拖放模式需要最小的移动距离
        var DRAG_MOVE_THRESHOLD = 10;

        if (!this._startPosition) return;

        var movement = kity.Vector.fromPoints(this._dragPosition || this._startPosition, position);
        var minder = this._minder;

        this._dragPosition = position;

        if (!this._dragMode) {
            // 判断拖放模式是否该启动
            if (GM.getDistance(this._dragPosition, this._startPosition) < DRAG_MOVE_THRESHOLD) {
                return;
            }
            if (!this._enterDragMode()) {
                return;
            }
        }

        for (var i = 0; i < this._dragSources.length; i++) {
            this._dragSources[i].setLayoutOffset(this._dragSources[i].getLayoutOffset().offset(movement));
            minder.applyLayoutResult(this._dragSources[i]);
        }
        
        if (!this._dropTest()) {
            this._orderTest();
        } else {
            this._renderOrderHint(this._orderSucceedHint = null);
        }
    },

    dragEnd: function() {
        this._startPosition = null;
        this._dragPosition = null;

        if (!this._dragMode) {
            return;
        }

        this._fadeDragSources(1);

        if (this._dropSucceedTarget) {

            this._dragSources.forEach(function(source) {
                source.setLayoutOffset(null);
            });
            
            this._minder.layout(-1);

            this._minder.execCommand('movetoparent', this._dragSources, this._dropSucceedTarget);

        } else if (this._orderSucceedHint) {

            var hint = this._orderSucceedHint;
            var index = hint.node.getIndex();

            var sourceIndexes = this._dragSources.map(function(source) {
                // 顺便干掉布局偏移
                source.setLayoutOffset(null);
                return source.getIndex();
            });

            var maxIndex = Math.max.apply(Math, sourceIndexes);
            var minIndex = Math.min.apply(Math, sourceIndexes);

            if (index < minIndex && hint.type == 'down') index++;
            if (index > maxIndex && hint.type == 'up') index--;

            hint.node.setLayoutOffset(null);

            this._minder.execCommand('arrange', this._dragSources, index);
            this._renderOrderHint(null);
        } else {
            this._minder.fire('savescene');
        }

        this._minder.layout(300);
        this._leaveDragMode();
        this._minder.fire('contentchange');
    },

    // 进入拖放模式：
    //    1. 计算拖放源和允许的拖放目标
    //    2. 标记已启动
    _enterDragMode: function() {
        this._calcDragSources();
        if (!this._dragSources.length) {
            this._startPosition = null;
            return false;
        }
        this._fadeDragSources(0.5);
        this._calcDropTargets();
        this._calcOrderHints();
        this._dragMode = true;
        this._minder.setStatus('dragtree');
        return true;
    },

    // 从选中的节点计算拖放源
    //    并不是所有选中的节点都作为拖放源，如果选中节点中存在 A 和 B，
    //    并且 A 是 B 的祖先，则 B 不作为拖放源
    //
    //    计算过程：
    //       1. 将节点按照树高排序，排序后只可能是前面节点是后面节点的祖先
    //       2. 从后往前枚举排序的结果，如果发现枚举目标之前存在其祖先，
    //          则排除枚举目标作为拖放源，否则加入拖放源
    _calcDragSources: function() {
        this._dragSources = this._minder.getSelectedAncestors();
    },

    _fadeDragSources: function(opacity) {
        var minder = this._minder;
        this._dragSources.forEach(function(source) {
            source.getRenderContainer().setOpacity(opacity, 200);
            source.traverse(function(node) {
                if (opacity < 1) {
                    minder.detachNode(node);
                } else {
                    minder.attachNode(node);
                }
            }, true);
        });
    },


    // 计算拖放目标可以释放的节点列表（释放意味着成为其子树），存在这条限制规则：
    //    - 不能拖放到拖放目标的子树上（允许拖放到自身，因为多选的情况下可以把其它节点加入）
    //
    //    1. 加入当前节点（初始为根节点）到允许列表
    //    2. 对于当前节点的每一个子节点：
    //       (1) 如果是拖放目标的其中一个节点，忽略（整棵子树被剪枝）
    //       (2) 如果不是拖放目标之一，以当前子节点为当前节点，回到 1 计算
    //    3. 返回允许列表
    //
    _calcDropTargets: function() {

        function findAvailableParents(nodes, root) {
            var availables = [],
                i;
            availables.push(root);
            root.getChildren().forEach(function(test) {
                for (i = 0; i < nodes.length; i++) {
                    if (nodes[i] == test) return;
                }
                availables = availables.concat(findAvailableParents(nodes, test));
            });
            return availables;
        }

        this._dropTargets = findAvailableParents(this._dragSources, this._minder.getRoot());
        this._dropTargetBoxes = this._dropTargets.map(function(source) {
            return source.getLayoutBox();
        });
    },

    _calcOrderHints: function() {
        var sources = this._dragSources;
        var ancestor = MinderNode.getCommonAncestor(sources);

        // 只有一个元素选中，公共祖先是其父
        if (ancestor == sources[0]) ancestor = sources[0].parent;

        if (sources.length === 0 || ancestor != sources[0].parent) {
            this._orderHints = [];
            return;
        }

        var siblings = ancestor.children;

        this._orderHints = siblings.reduce(function(hint, sibling) {
            if (sources.indexOf(sibling) == -1) {
                hint = hint.concat(sibling.getOrderHint());
            }
            return hint;
        }, []);
    },

    _leaveDragMode: function() {
        this._dragMode = false;
        this._dropSucceedTarget = null;
        this._orderSucceedHint = null;
        this._renderDropHint(null);
        this._renderOrderHint(null);
        this._minder.rollbackStatus();
    },

    _drawForDragMode: function() {
        this._text.setContent(this._dragSources.length + ' items');
        this._text.setPosition(this._startPosition.x, this._startPosition.y + 5);
        this._minder.getRenderContainer().addShape(this);
    },

    _boxTest: function(targets, targetBoxMapper, judge) {
        var sourceBoxes = this._dragSources.map(function(source) {
            return source.getLayoutBox();
        });

        var i, j, target, sourceBox, targetBox;

        judge = judge || function(intersectBox, sourceBox, targetBox) {
            return intersectBox;
        };

        for (i = 0; i < targets.length; i++) {

            target = targets[i];
            targetBox = targetBoxMapper.call(this, target, i);

            for (j = 0; j < sourceBoxes.length; j++) {
                sourceBox = sourceBoxes[j];

                var intersectBox = GM.getIntersectBox(sourceBox, targetBox);
                if (judge(intersectBox, sourceBox, targetBox)) {
                    return target;
                }
            }
        }

        return null;
    },

    _dropTest: function() {
        this._dropSucceedTarget = this._boxTest(this._dropTargets, function(target, i) {
            return this._dropTargetBoxes[i];
        }, function(intersectBox, sourceBox, targetBox) {
            function area(box) {
                return box.width * box.height;
            }
            if (!intersectBox) return false;
            // 面积判断
            if (area(intersectBox) > 0.5 * Math.min(area(sourceBox), area(targetBox))) return true;
            if (intersectBox.width + 1 >= Math.min(sourceBox.width, targetBox.width)) return true;
            if (intersectBox.height + 1 >= Math.min(sourceBox.height, targetBox.height)) return true;
            return false;
        });
        this._renderDropHint(this._dropSucceedTarget);
        return !!this._dropSucceedTarget;
    },

    _orderTest: function() {
        this._orderSucceedHint = this._boxTest(this._orderHints, function(hint) {
            return hint.area;
        });
        this._renderOrderHint(this._orderSucceedHint);
        return !!this._orderSucceedHint;
    },

    _renderDropHint: function(target) {
        this._dropHinter.render(target);
    },

    _renderOrderHint: function(hint) {
        this._orderHinter.render(hint);
    },
    preventDragMove: function() {
        this._startPosition = null;
    }
});

KityMinder.registerModule('DragTree', function() {
    var dragger;

    return {
        init: function() {
            dragger = new TreeDragger(this);
            window.addEventListener('mouseup', function() {
                dragger.dragEnd();
            });
        },
        events: {
            'normal.mousedown inputready.mousedown': function(e) {
                // 单选中根节点也不触发拖拽
                if (e.originEvent.button) return;
                if (e.getTargetNode() && e.getTargetNode() != this.getRoot()) {
                    dragger.dragStart(e.getPosition(this.getRenderContainer()));
                }
            },
            'normal.mousemove dragtree.mousemove': function(e) {
                dragger.dragMove(e.getPosition(this.getRenderContainer()));
            },
            'normal.mouseup dragtree.beforemouseup': function(e) {
                dragger.dragEnd();
                //e.stopPropagation();
                e.preventDefault();
            },
            'statuschange': function(e) {
                if (e.lastStatus == 'textedit' && e.currentStatus == 'normal') {
                    dragger.preventDragMove();
                }
            }
        },
        commands: {
            'movetoparent': MoveToParentCommand
        }
    };
});
KityMinder.registerModule('KeyboardModule', function() {
    var min = Math.min,
        max = Math.max,
        abs = Math.abs,
        sqrt = Math.sqrt,
        exp = Math.exp;

    function buildPositionNetwork(root) {
        var pointIndexes = [],
            p;
        root.traverse(function(node) {
            p = node.getLayoutBox();

            // bugfix: 不应导航到收起的节点（判断其尺寸是否存在）
            if (p.width && p.height) {
                pointIndexes.push({
                    left: p.x,
                    top: p.y,
                    right: p.x + p.width,
                    bottom: p.y + p.height,
                    width: p.width,
                    height: p.height,
                    node: node,
                    text: node.getText()
                });
            }
        });
        for (var i = 0; i < pointIndexes.length; i++) {
            findClosestPointsFor(pointIndexes, i);
        }
    }

    // 这是金泉的点子，赞！
    // 求两个不相交矩形的最近距离
    function getCoefedDistance(box1, box2) {
        var xMin, xMax, yMin, yMax, xDist, yDist, dist, cx, cy;
        xMin = min(box1.left, box2.left);
        xMax = max(box1.right, box2.right);
        yMin = min(box1.top, box2.top);
        yMax = max(box1.bottom, box2.bottom);

        xDist = xMax - xMin - box1.width - box2.width;
        yDist = yMax - yMin - box1.height - box2.height;

        if (xDist < 0) dist = yDist;
        else if (yDist < 0) dist = xDist;
        else dist = sqrt(xDist * xDist + yDist * yDist);

        return {
            cx: dist,
            cy: dist
        };
    }

    function findClosestPointsFor(pointIndexes, iFind) {
        var find = pointIndexes[iFind];
        var most = {},
            quad;
        var current, dist;

        for (var i = 0; i < pointIndexes.length; i++) {

            if (i == iFind) continue;
            current = pointIndexes[i];

            dist = getCoefedDistance(current, find);

            // left check
            if (current.right < find.left) {
                if (!most.left || dist.cx < most.left.dist) {
                    most.left = {
                        dist: dist.cx,
                        node: current.node
                    };
                }
            }

            // right check
            if (current.left > find.right) {
                if (!most.right || dist.cx < most.right.dist) {
                    most.right = {
                        dist: dist.cx,
                        node: current.node
                    };
                }
            }

            // top check
            if (current.bottom < find.top) {
                if (!most.top || dist.cy < most.top.dist) {
                    most.top = {
                        dist: dist.cy,
                        node: current.node
                    };
                }
            }

            // bottom check
            if (current.top > find.bottom) {
                if (!most.down || dist.cy < most.down.dist) {
                    most.down = {
                        dist: dist.cy,
                        node: current.node
                    };
                }
            }
        }
        find.node._nearestNodes = {
            right: most.right && most.right.node || null,
            top: most.top && most.top.node || null,
            left: most.left && most.left.node || null,
            down: most.down && most.down.node || null
        };
    }

    function navigateTo(km, direction) {
        var referNode = km.getSelectedNode();
        if (!referNode) {
            km.select(km.getRoot());
            buildPositionNetwork(km.getRoot());
            return;
        }
        if (!referNode._nearestNodes) {
            buildPositionNetwork(km.getRoot());
        }
        var nextNode = referNode._nearestNodes[direction];
        if (nextNode) {
            km.select(nextNode, true);
        }
    }

    var NavigateToParentCommand = kity.createClass({
        base: Command,

        execute: function(km) {
            var node = km.getSelectedNode();
            if (node && node.parent) {
                km.select(node.parent, true);
            }
            this.setContentChanged(false);
        },

        queryState: function(km) {
            return km.getSelectedNode() ? 0 : -1;
        },

        enableReadOnly: true
    });

    // 稀释用
    var lastFrame;
    return {
        'commands': {
            'navparent': NavigateToParentCommand
        },
        'commandShortcutKeys': {
            'navparent': 'shift+tab'
        },
        'events': {
            'layoutallfinish': function() {
                var root = this.getRoot();
                buildPositionNetwork(root);
            },
            'normal.keydown readonly.keydown': function(e) {
                var minder = this;
                ['left', 'right', 'up', 'down'].forEach(function(key) {
                    if (e.isShortcutKey(key)) {
                        navigateTo(minder, key == 'up' ? 'top' : key);
                    }
                });
            },
            'normal.keyup': function(e) {
                if (browser.ipad) {
                    var keys = KityMinder.keymap;
                    var node = e.getTargetNode();
                    var lang = this.getLang();

                    if (this.receiver) this.receiver.keydownNode = node;

                    var keyEvent = e.originEvent;

                    if (keyEvent.altKey || keyEvent.ctrlKey || keyEvent.metaKey || keyEvent.shiftKey) return;

                    switch (keyEvent.keyCode) {
                        case keys.Enter:
                            this.execCommand('AppendSiblingNode', lang.topic);
                            e.preventDefault();
                            break;

                        case keys.Backspace:
                        case keys.Del:
                            e.preventDefault();
                            this.execCommand('RemoveNode');
                            break;

                    }
                }

            }
        }
    };
});
KityMinder.registerModule('Select', function() {
    var minder = this;
    var rc = minder.getRenderContainer();
    var g = KityMinder.Geometry;

    // 在实例上渲染框选矩形、计算框选范围的对象
    var marqueeActivator = (function() {

        // 记录选区的开始位置（mousedown的位置）
        var startPosition = null;

        // 选区的图形
        var marqueeShape = new kity.Path();

        // 标记是否已经启动框选状态
        //    并不是 mousedown 发生之后就启动框选状态，而是检测到移动了一定的距离（MARQUEE_MODE_THRESHOLD）之后
        var marqueeMode = false;
        var MARQUEE_MODE_THRESHOLD = 10;

        return {
            selectStart: function(e) {
                // 只接受左键
                if (e.originEvent.button || e.originEvent.altKey) return;

                // 清理不正确状态
                if (startPosition) {
                    return this.selectEnd();
                }

                startPosition = g.snapToSharp(e.getPosition(rc));
            },
            selectMove: function(e) {
                if (minder.getStatus() == 'textedit') {
                    return;
                }
                if (!startPosition) return;

                var p1 = startPosition,
                    p2 = e.getPosition(rc);

                // 检测是否要进入选区模式
                if (!marqueeMode) {
                    // 距离没达到阈值，退出
                    if (g.getDistance(p1, p2) < MARQUEE_MODE_THRESHOLD) {
                        return;
                    }
                    // 已经达到阈值，记录下来并且重置选区形状
                    marqueeMode = true;
                    rc.addShape(marqueeShape);
                    marqueeShape
                        .fill(minder.getStyle('marquee-background'))
                        .stroke(minder.getStyle('marquee-stroke')).setOpacity(0.8).getDrawer().clear();
                }

                var marquee = g.getBox(p1, p2),
                    selectedNodes = [];

                // 使其犀利
                marquee.left = Math.round(marquee.left);
                marquee.top = Math.round(marquee.top);
                marquee.right = Math.round(marquee.right);
                marquee.bottom = Math.round(marquee.bottom);

                // 选区形状更新
                marqueeShape.getDrawer().pipe(function() {
                    this.clear();
                    this.moveTo(marquee.left, marquee.top);
                    this.lineTo(marquee.right, marquee.top);
                    this.lineTo(marquee.right, marquee.bottom);
                    this.lineTo(marquee.left, marquee.bottom);
                    this.close();
                });

                // 计算选中范围
                minder.getRoot().traverse(function(node) {
                    var renderBox = node.getLayoutBox();
                    if (g.getIntersectBox(renderBox, marquee)) {
                        selectedNodes.push(node);
                    }
                });

                // 应用选中范围
                minder.select(selectedNodes, true);

                // 清除多余的东西
                window.getSelection().removeAllRanges();
            },
            selectEnd: function(e) {
                if (startPosition) {
                    startPosition = null;
                }
                if (marqueeMode) {
                    marqueeShape.fadeOut(200, 'ease', 0, function() {
                        if (marqueeShape.remove) marqueeShape.remove();
                    });
                    marqueeMode = false;
                }
            }
        };
    })();

    var lastDownNode = null, lastDownPosition = null;
    return {
        'init': function() {
            window.addEventListener('mouseup', function() {
                marqueeActivator.selectEnd();
            });
        },
        'events': {
            'mousedown': function(e) {

                var downNode = e.getTargetNode();

                // 没有点中节点：
                //     清除选中状态，并且标记选区开始位置
                if (!downNode) {
                    this.removeAllSelectedNodes();
                    marqueeActivator.selectStart(e);

                    this.setStatus('normal');
                }

                // 点中了节点，并且按了 shift 键：
                //     被点中的节点切换选中状态
                else if (e.originEvent.shiftKey) {
                    this.toggleSelect(downNode);
                }

                // 点中的节点没有被选择：
                //     单选点中的节点
                else if (!downNode.isSelected()) {
                    this.select(downNode, true);
                }

                // 点中的节点被选中了，并且不是单选：
                //     完成整个点击之后需要使其变为单选。
                //     不能马上变为单选，因为可能是需要拖动选中的多个节点
                else if (!this.isSingleSelect()) {
                    lastDownNode = downNode;
                    lastDownPosition = e.getPosition(this.getRenderContainer());
                }
            },
            'mousemove': marqueeActivator.selectMove,
            'mouseup': function(e) {
                var upNode = e.getTargetNode();

                // 如果 mouseup 发生在 lastDownNode 外，是无需理会的
                if (upNode && upNode == lastDownNode) {
                    var upPosition = e.getPosition(this.getRenderContainer());
                    var movement = kity.Vector.fromPoints(lastDownPosition, upPosition);
                    if (movement.length() < 1) this.select(lastDownNode, true);
                    lastDownNode = null;
                }

                // 清理一下选择状态
                marqueeActivator.selectEnd(e);
            },
            //全选操作
            'normal.keydown inputready.keydown':function(e){

                if ( e.isShortcutKey('ctrl+a') ){
                    var selectedNodes = [];

                    this.getRoot().traverse(function(node){
                        selectedNodes.push(node);
                    });
                    this.select(selectedNodes,true);
                    e.preventDefault();
                }
            }
        }
    };
});
/* global Renderer: true */

KityMinder.registerModule('TextEditModule', function() {
    var km = this;
    var sel = new Minder.Selection();
    var range = new Minder.Range();
    var receiver = new Minder.Receiver(this,sel,range);
    var keyboarder = new Minder.keyboarder(receiver);

    this.receiver = receiver;

    //鼠标被点击，并未太抬起时为真
    var mouseDownStatus = false;

    var dblclickEvent = false;
    //当前是否有选区存在
    var selectionReadyShow = false;

    var mousedownNode,mouseupTimer,mousedownTimer;

    var lastMinderNode;
    function inputStatusReady(node){
        if (node && km.isSingleSelect() && node.isSelected()) {

            node.getTextGroup().setStyle('cursor','default');

            var color = node.getStyle('text-selection-color');

            //准备输入状态

            receiver.updateByMinderNode(node);

            sel.setHide()
                .setStartOffset(0)
                .setEndOffset(receiver.getTxtOfContainer().length)
                .setColor(color);


            receiver.updateContainerRangeBySel();

            if(browser.ie ){
                var timer = setInterval(function(){
                    var nativeRange = range.nativeSel.getRangeAt(0);
                    if(!nativeRange || nativeRange.collapsed){
                        range.select();
                    }else {
                        clearInterval(timer);
                    }
                });
            }


            km.setStatus('inputready');

        }

    }

    km.textEditNode = function(node){
        inputStatusReady(node);
        km.setStatus('textedit');
        receiver.updateSelection();
    };
    return {
        'events': {
            'ready': function() {
                document.body.appendChild(receiver.container);
            },

            'normal.beforemousedown textedit.beforemousedown inputready.beforemousedown': function(e) {
                //右键直接退出
                if (e.isRightMB()) {
                    if (!e.getTargetNode()) this.setStatus('normal');
                    return;
                }


                mouseDownStatus = true;

                selectionReadyShow = sel.isShow();

                sel.setHide();

                var node = e.getTargetNode();

                //点击在之前的选区上
                if (!node) {
                    var selectionShape = e.kityEvent.targetShape;
                    if (selectionShape && selectionShape.getType() == 'Selection') {
                        node = receiver.getMinderNode();
                        e.stopPropagationImmediately();
                    }

                }

                if(node){

                    if (this.isSingleSelect() && node.isSelected()) {
                        lastMinderNode = node;

                        mousedownNode = node;

                        var textGroup = node.getTextGroup();


                        sel.collapse(true);
                        sel.setColor(node.getStyle('text-selection-color'));

                        receiver
                            .updateByMinderNode(node)
                            .updateIndexByMouse(e.getPosition(node.getRenderContainer()))
                            .setRange(range)
                            .setReady();

                        if(selectionReadyShow){


                            sel.setShowStatus();
                            clearTimeout(mousedownTimer);

                            mousedownTimer = setTimeout(function() {
                                if(dblclickEvent){
                                    dblclickEvent = false;
                                    return;
                                }

                                sel.collapse(true)
                                    .updatePosition(receiver.getOffsetByIndex())
                                    .setShow();
                                textGroup.setStyle('cursor','text');

                            },200);
                            km.setStatus('textedit');

                        }

                        return;

                    }
                }
                //模拟光标没有准备好
                receiver.clearReady();
                //当点击空白处时，光标需要消失
                receiver.clear();


                if(lastMinderNode){

                    lastMinderNode.getTextGroup().setStyle('cursor','default');
                }

            },
            'inputready.keyup':function(){
                if(sel.isHide()){
                    inputStatusReady(this.getSelectedNode());
                }
            },

            //当节点选区通过键盘发生变化时，输入状态要准备好
            'normal.keyup': function(e) {
                var node = this.getSelectedNode();
                var keyCode = e.getKeyCode();
                if (node) {
                    if (this.isSingleSelect() && node.isSelected() && !sel.isShow() ) {
                        var orgEvt = e.originEvent;
                        if (keymap.isSelectedNodeKey[keyCode] &&
                            !orgEvt.ctrlKey &&
                            !orgEvt.metaKey &&
                            !orgEvt.shiftKey &&
                            !orgEvt.altKey) {
                                inputStatusReady(node);

                        }
                    }
                }
            },
            'normal.mouseup textedit.mouseup inputready.mouseup': function(e) {

                mouseDownStatus = false;

                var node = e.getTargetNode();

                mousedownNode = null;

                if (node && !selectionReadyShow && receiver.isReady()) {

                    sel.collapse(true);

                    sel.setColor(node.getStyle('text-selection-color'));

                    //必须再次focus，要不不能呼出键盘
                    if(browser.ipad){
                        receiver.focus();
                    }
                    clearTimeout(mouseupTimer);
                    mouseupTimer = setTimeout(function() {
                        if(dblclickEvent){
                            dblclickEvent = false;
                            return;
                        }
                        sel.collapse(true)
                            .updatePosition(receiver.getOffsetByIndex())
                            .setShow();
                        node.getTextGroup().setStyle('cursor','text');
                    }, 200);


                    km.setStatus('textedit');

                    return;
                }
                //当选中节点后，输入状态准备
                if(sel.isHide()){
                    inputStatusReady(e.getTargetNode());
                }else {
                    //当有光标时，要同步选区
                    if(!sel.collapsed){
                        receiver.updateContainerRangeBySel();
                    }
                }




            },
            'textedit.beforemousemove inputready.beforemousemove': function(e) {

                if(browser.ipad){
                    return;
                }
                //ipad下不做框选
                if (mouseDownStatus && receiver.isReady() && selectionReadyShow) {


                    e.stopPropagationImmediately();

                    if(mousedownNode){

                        var offset = e.getPosition( mousedownNode.getRenderContainer());

                        receiver
                            .updateSelectionByMousePosition(offset)
                            .updateSelection(offset);
                        setTimeout(function(){
                            receiver.updateContainerRangeBySel();
                        });


                    }


                }else if(mouseDownStatus && !selectionReadyShow){
                    //第一次点中，第二次再次点中进行拖拽
                    km.setStatus('normal');
                    receiver.clearReady();
                }
            },
            'normal.dblclick textedit.dblclick inputready.dblclick': function(e) {

                var node = e.getTargetNode();
                dblclickEvent = true;
                if(node){
                    //跟mouseup的timeout有冲突，这里做标记处理


                    inputStatusReady(node);

                    km.setStatus('textedit');

                    receiver.updateSelection();

                }

            },
            'restoreScene': function() {
                receiver.clear();
                inputStatusReady(this.getSelectedNode());
            },
            'stopTextEdit': function() {
                receiver.clear();
                km.setStatus('normal');
            },
            'resize': function(e) {
                sel.setHide();
            },
            'execCommand': function(e) {
                var cmds = {
                    'appendchildnode': 1,
                    'appendsiblingnode': 1,
                    'editnode': 1
                };
                if (cmds[e.commandName]) {
                    inputStatusReady(km.getSelectedNode());
                    receiver.updateSelection();
                    return;

                }

                if(sel.isShow()){
                    receiver.updateTextOffsetData().updateSelection();
                }
            },
            'layoutfinish':function(e){
                if (e.node === receiver.minderNode && (this.getStatus() == 'textedit' || this.getStatus() == 'inputready') ) {//&& selectionReadyShow
                    receiver.setContainerStyle();
                }
            },
            'selectionclear': function() {
                var node = km.getSelectedNode();
                if(node){
                    inputStatusReady(node);
                }else{
                    km.setStatus('normal');
                    receiver.clear();
                }


            },
            'blur': function() {
               !/\?debug#?/.test(location.href) && receiver.clear();
            },
            'textedit.import': function() {
                km.setStatus('normal');
                receiver.clear();
            },
            'inputready.mousewheel textedit.mousewheel': function() {
                receiver.setContainerStyle();
            },
            'statuschange':function(e){
                if(e.lastStatus == 'textedit'){

                    this.fire('contentchange');
                }
            }

        }
    };
});
//接收者
Minder.keyboarder = kity.createClass('keyboarder', function(){


    return {
        constructor: function(re) {
            this.re = re;
            this.container = re.container;
            this.selection = re.selection;
            this.range = re.range;
            this.km = re.km;
            this.lastMinderNode = null;
            this.isTypeText = false;
            this._initEvent();
            this.isShortcutCopyKey = false;
        },
        //给接受容器绑定事件
        _initEvent: function(){
            var me = this;
            if(browser.ipad) {
                utils.listen(this.container, 'keydown keypress keyup input', function(e) {
                    me._handleEvents.call(me, new MinderEvent(e.type == 'keyup' ? 'beforekeyup' : e.type, e));
                    if(e.type == 'keyup'){
                        if(me.km.getStatus() == 'normal'){
                            me.km.fire( 'keyup', e);
                        }
                    }

                });
            }
            this.km.on('inputready.beforekeyup ' +
                'inputready.beforekeydown ' +
                'textedit.beforekeyup ' +
                'normal.keydown ' +
                'normal.keyup ' +
                'textedit.beforekeydown ' +
                'textedit.keypress ' +
                'textedit.paste',
            utils.proxy(this._handleEvents, this));
        },
        _handleEvents:function(e){
            switch (e.type) {
                case 'input':
                    this._input(e);
                    break;
                case 'beforekeydown':
                    this._beforeKeydown(e);
                    break;
                case 'beforekeyup':
                    this._beforeKeyup(e);
                    break;
                case 'keyup':
                    this._keyup(e);
            }
        },
        _setTextToContainer : function(keyCode,iskeyUp){
            var me = this;
            //同步节点
            me.minderNode = me.re.minderNode;

            clearTimeout(me.timer);

            if (!me.range.hasNativeRange()) {
                return;
            }


            if(keymap.controlKeys[keyCode] && !iskeyUp ){
                return;
            }
            //当第一次输入内容时进行保存
            if(me.lastMinderNode !== me.minderNode && !keymap.notContentChange[keyCode]){
                me.km.fire('saveScene',{
                    inputStatus:true
                });
                me.lastMinderNode = me.minderNode;
            }

            var text = me.re.getTxtOfContainer();

//            //#46 修复在ff下定位到文字后方空格光标不移动问题
//            if (browser.gecko && /\s$/.test(text)) {
//                text += '\u200b';
//            }

            //重新渲染节点
            me.minderNode.setText(text);
            me.re.setContainerStyle();
            me.minderNode.getRenderContainer().bringTop();
            me.minderNode.render();

            //移动光标不做layout
            if(!keymap.notContentChange[keyCode]){
                clearTimeout(me.inputTextTimer);

                me.inputTextTimer = setTimeout(function(){
                    me.km.layout(300);
                },300);
            }


            me.re.updateTextOffsetData()
                .updateRange()
                .updateSelectionByRange();

            me.selection
                .updateOffsetByTextData(me.re.textData)
                .updatePosition();


            //当然inputready状态时，如果输入文字，节点内文本会被先选中然后再消失，体验不好
            if(me.km.getStatus() != 'inputready'){
                me.selection.setHoldShow();
            }


            me.timer = setTimeout(function() {
                if(me.selection.isShow()){
                    me.selection.setShow();

                }

            }, 200);
            me.km.setStatus('textedit');
        },
        _input:function(){
            var me = this;
            if (browser.ipad) {
                setTimeout(function() {
                    me._setTextToContainer();
                });
            }
        },
        _beforeKeydown:function(e){
            var me = this;
            var orgEvt = e.originEvent;
            var keyCode = orgEvt.keyCode;

            this.isTypeText = keyCode == 229 || keyCode === 0;

            switch (keyCode) {
                case keymap.Enter:
                    if(e.originEvent.shiftKey && me.selection.isShow()){
                        me._handlerEnterkey();
                        e.preventDefault();
                        return false;
                    };
                case keymap.Tab:
                case keymap.Insert:
                    if(this.selection.isShow()){
                        this.re.clear();
                        this.km.setStatus('inputready');
                        clearTimeout(me.inputTextTimer);
                        e.preventDefault();
                    }else{
                        this.km.setStatus('normal');
                        this.km.fire('contentchange');
                    }

                    return;
                case keymap.left:
                case keymap.right:
                case keymap.up:
                case keymap.down:
                case keymap.Backspace:
                case keymap.Del:
                case keymap['/']:
                case keymap.F2:
                case keymap.Insert:
                    if(this.selection.isHide()){
                        this.km.setStatus('normal');
                        return;
                    }
                    break;
                case keymap.Control:
                case keymap.Alt:
                case keymap.Cmd:

                    if(this.selection.isHide() && this.km.getStatus() != 'textedit' && this.km.getStatus() !='inputready'){
                        this.km.setStatus('normal');
                        return;
                    }

            }

            if (e.originEvent.ctrlKey || e.originEvent.metaKey) {

                //选中节点时的复制粘贴，要变成normal
                if(this.selection.isHide() && {
                    86:1,
                    88:1,
                    67:1
                }[keyCode]){
                    //修正在cvs方式下_keyup会把节点文字选中
                    this.isShortcutCopyKey = true;
                    this.km.setStatus('normal');
                    return;
                }

                //粘贴
                if (keyCode == keymap.v) {

                    setTimeout(function () {
                        me.range.updateNativeRange().insertNode($('<span>$$_kityminder_bookmark_$$</span>')[0]);
                        var brArr = [];
                        utils.each(me.container.getElementsByTagName('br'),function(i,br){
                            brArr.push(br);
                        });
                        utils.each(brArr,function(i,br){
                            var textNode = document.createTextNode('\n');
                            br.parentNode.insertBefore(textNode,br);
                            br.parentNode.removeChild(br);
                        });
                        var textContent = me.container.textContent.replace(/[\u200b\t\r]/g,'');
                        var index = textContent.indexOf('$$_kityminder_bookmark_$$');

                        me.re.setContainerTxt(textContent.replace('$$_kityminder_bookmark_$$',''));


                        me.range.setStartOffset(index).collapse(true).select();
                        me._setTextToContainer(keyCode);
                    },50);
                    return;
                }
                //剪切
                if (keyCode == keymap.x) {
                    setTimeout(function () {
                        me._setTextToContainer(keyCode);
                    },50);
                    return;
                }


            }

            this.isShortcutCopyKey = false;
            //针对不能连续删除做处理
            //if(keymap.Del  == keyCode || keymap.Backspace == keyCode)
            //    me._setTextToContainer(keyCode);

            me._setTextToContainer(keyCode);
        },
        _beforeKeyup:function(e){
            var me = this;
            var orgEvt = e.originEvent;
            var keyCode = orgEvt.keyCode;

            switch (keyCode) {
                case keymap.Enter:
                case keymap.Tab:
                case keymap.Insert:
                case keymap.F2:
                    if(browser.ipad){
                        if(this.selection.isShow()){
                            this.re.clear();
                            this.km.setStatus('inputready');
                            clearTimeout(me.inputTextTimer);
                            e.preventDefault();
                        }else{
                            this.km.setStatus('normal');
                            this.km.fire('contentchange');
                        }
                        return;
                    }

                    if (keymap.Enter == keyCode && (this.isTypeText || browser.mac && browser.gecko)) {
                        me._setTextToContainer(keyCode,true);
                    }
                    if (this.re.keydownNode === this.re.minderNode) {
                        this.km.rollbackStatus();
                        this.re.clear();
                    }
                    e.preventDefault();
                    return;
                case keymap.Del:
                case keymap.Backspace:
                case keymap.Spacebar:
                    if(browser.ipad){
                        if(this.selection.isHide()){
                            this.km.setStatus('normal');
                            return;
                        }

                    }
                    me._setTextToContainer(keyCode,true);
                    return;
            }

            if (this.isTypeText) {
                me._setTextToContainer(keyCode,true);
                return;
            }
            if (browser.mac && browser.gecko){
                me._setTextToContainer(keyCode,true);
                return;
            }

            me._setTextToContainer(keyCode,true);

            return true;
        },
        _keyup:function(e){
            var me = this;
            var timer;
            var node = this.km.getSelectedNode();
            if(this.km.getStatus() == 'normal' && node && this.selection.isHide()){

                if(this.isShortcutCopyKey){
                    return;
                }


                if (node && this.km.isSingleSelect() && node.isSelected()) {

                    this.re.updateByMinderNode(node);

                    this.selection.setHide()
                        .setStartOffset(0)
                        .setEndOffset(this.re.getTxtOfContainer().length)
                        .setColor( node.getStyle('text-selection-color'));

                    setTimeout(function(){
                        me.re.updateContainerRangeBySel();
                    });

                    if(browser.ie ){
                        timer = setInterval(function(){
                            var nativeRange = me.range.nativeSel.getRangeAt(0);
                            if(!nativeRange || nativeRange.collapsed){
                                me.range.select();
                            }else {
                                clearInterval(timer);
                            }
                        });
                    }

                    this.km.setStatus('inputready');

                }
            }
        },
        //处理软回车操作
        _handlerEnterkey:function(){
            function removeTmpTextNode(node){
                if(node && node.nodeType == 3 && node.nodeValue.length === 0){
                    node.parentNode.removeChild(node);
                }
            }
            var rng = this.range;
            var br = document.createElement('br');
            var me = this;
            if(!rng.collapsed){
                rng.deleteContents();
            }
            rng.insertNode(br);
            removeTmpTextNode(br.previousSibling);
            removeTmpTextNode(br.nextSibling);
            rng.setStartAfter(br);
            rng.collapse(true);

            var start = rng.startContainer.childNodes[rng.startOffset];
            if(!start){
                br = br.cloneNode(false);
                rng.startContainer.appendChild(br);
                rng.setStartBefore(br);
                rng.collapse(true);
            }


            rng.select();
            me._setTextToContainer(keymap.Enter);


        }

    };
}());
Minder.Range = kity.createClass('Range',function(){

    function getOffset(rng,dir){
        var node = rng[dir + 'Container'],
            offset =  rng[dir + 'Offset'],
            rOffset = 0;
        if(node.nodeType == 1){
            //默认不会出现得不到子节点的情况
            node = node.childNodes[offset];
            if(!node && rng.startContainer && rng.startContainer.nodeName == 'DIV' ) {
                rng.startContainer.innerHTML = '<p></p>';

                offset = 0;

            }else if(node.nodeType == 3){
                offset = 0;
            }
        }
        utils.each(rng.container.childNodes,function(index,n){
            if(n === node){
                if(n.nodeType == 1){
                    return false;
                }else{
                    rOffset += offset;
                    return false;
                }
            }
            rOffset += (n.nodeType == 1 ? 1 : utils.clearWhitespace(n.nodeValue).length);
        });
        return rOffset;
    }
    function setBoundary(rng,offset,dir){
        var rOffset = 0,cont = rng.container;
        utils.each(cont.childNodes,function(index,node){
            if(node.nodeType == 1){
                if(rOffset == offset){

                    rng['set' + dir](cont,index);
                    return false;
                }
                rOffset++;

                return;
            }

            var currentLength = utils.clearWhitespace(node.nodeValue).length;
            if(rOffset + currentLength  >= offset){
                rng['set' + dir](node,offset - rOffset);
                return false;
            }
            rOffset += currentLength;
        });
    }
    return {
        constructor : function(container){
            this.nativeRange = document.createRange();
            this.nativeSel = window.getSelection();
            this.startContainer =
                this.endContainer =
                    this.startOffset =
                        this.endOffset = null;
            this.collapsed = false;
            this.container = container || null;
        },
        hasNativeRange : function(){
            return this.nativeSel.rangeCount !== 0 ;
        },
        deleteContents : function(){
            this.nativeRange.deleteContents();
            return this._updateBoundary();
        },
        select:function(){

            var start = this.nativeRange.startContainer;
            if(start.nodeType == 1 && start.childNodes.length === 0){
                var char = document.createTextNode('\u200b');
                start.appendChild(char);
                this.nativeRange.setStart(char,1);
                this.nativeRange.collapse(true);
            }else if(this.collapsed && start.nodeType == 1){
                start = start.childNodes[this.startOffset];
                if(start && start.nodeType == 3 && start.nodeValue.length === 0){

                    this.nativeRange.setStart(start,1);
                    this.nativeRange.collapse(true);
                }
            }
            try{
                this.nativeSel.removeAllRanges();
            }catch(e){

            }

            this.nativeSel.addRange(this.nativeRange);
            return this;
        },
        _updateBoundary : function(){
            var nRange = this.nativeRange;
            this.startContainer = nRange.startContainer;
            this.startContainer = nRange.startContainer;
            this.endContainer = nRange.endContainer;
            this.startOffset = nRange.startOffset;
            this.endOffset = nRange.endOffset;
            this.collapsed = nRange.collapsed;
            return this;
        },
        setStartOffset:function(offset){

            setBoundary(this,offset,'Start');
            return this;
        },
        setEndOffset:function(offset){

            setBoundary(this,offset,'End');
            return this;
        },
        setStart:function(node,offset){
            this.nativeRange.setStart(node,offset);
            this._updateBoundary();
            return this;
        },
        setStartAfter:function(node){
            return this.setStart(node.parentNode,utils.getNodeIndex(node) + 1);
        },
        setStartBefore:function(node){
            return this.setStart(node.parentNode,utils.getNodeIndex(node));
        },
        setEnd:function(node,offset){
            this.nativeRange.setEnd(node,offset);
            this._updateBoundary();
            return this;
        },
        update:function(){
            this.updateNativeRange()
                ._updateBoundary();

            return  this;
        },
        getStart:function(){
            this.update();
            return {
                startContainer:this.startContainer,
                startOffset:this.startOffset
            };
        },
        getStartOffset:function(){
            return getOffset(this,'start');
        },
        getEndOffset:function(){
            return getOffset(this,'end');
        },

        collapse:function(toStart){
            this.nativeRange.collapse(toStart === true);
            this._updateBoundary();
            return this;
        },
        isCollapsed:function(){
            this._updateBoundary();
            return this.collapsed;
        },
        insertNode:function(node){
            this.nativeRange.insertNode(node);

            return this._updateBoundary();
        },
        updateNativeRange:function(){

            this.nativeRange = this.nativeSel.getRangeAt(0);
            return this;
        },
        clear : function(){
            this.nativeSel.removeAllRanges();
            return this;
        }

    };
}());
//接收者
Minder.Receiver = kity.createClass('Receiver', {
    clear: function() {
        this.container.innerHTML = '';
        if (this.selection) {
            this.selection.setHide();
        }
        if (this.range) {
            this.range.clear();
        }
        this.index = 0;


        return this;
    },
    constructor: function(km,sel,range) {
        //初始化接收者
        this.setKityMinder(km);

        //创建接收者容器
        var _div = document.createElement('div');
        _div.setAttribute('contenteditable', true);
        _div.className = 'km_receiver';
        this.container = _div;
        utils.addCssRule('km_receiver_css',
            ' .km_receiver{white-space:nowrap;position:absolute;padding:0;margin:0;word-wrap:break-word;'
                + (/\?debug#?/.test(location.href)?'':'clip:rect(1em 1em 1em 1em);'));

        this.index = 0;
        this.selection = sel;
        this.range = range;

        this.range.container = _div;
    },
    setRange: function(range, index) {

        this.index = index || this.index;

        this.range = range;


        range.setStartOffset(this.index);
        range.collapse(true);
        var me = this;

        setTimeout(function() {
            me.container.focus();
            range.select();
        });
        return this;
    },
    setTextGroup: function(textGroup) {
        this.textGroup = textGroup;
        return this;
    },

    setKityMinder: function(km) {
        this.km = km;
        return this;
    },

    updateByMinderNode:function(node){
        this.setMinderNode(node);
        //追加selection到节点
        this._addSelection();
        //更新minderNode下的textGroup
        this.setTextGroup(node.getTextGroup());
        //更新接受容器的样式
        this.setContainerStyle();
        //更新textOffsetData数据
        this.updateTextOffsetData();
        //更新选取高度
        this.setSelectionHeight();
        //更新接收容器内容
        this.setContainerTxt();
        return this;
    },
    setMinderNode: function(node) {
        this.minderNode = node;
        this.selection.setMinderNode(node);
        return this;
    },
    _addSelection:function(){
        if (this.selection.container){
            this.selection.remove();
        }

        this.minderNode.getRenderContainer().addShape(this.selection);
        return this;
    },
    getMinderNode:function(){
        return this.minderNode;
    },
    
    updateIndex: function() {
        this.index = this.range.getStartOffset();
        return this;
    },

    setSelection: function(selection) {
        this.selection = selection;
        return this;
    },
    updateSelection: function(offset) {
        this.selection.update(this.textData,offset);
        return this;
    },
    getOffsetByIndex:function(index){
        return utils.getValueByIndex(this.textData, index !== undefined ? index : this.index);
    },
    getBaseOffset: function() {
        var offset = this.textGroup.getRenderBox('screen');
        return offset;
    },
    setContainerStyle: function() {
        var textGroupBox = this.getBaseOffset();
        this.container.style.cssText = ';left:' + (browser.ipad ? '-' : '') +
            textGroupBox.x + 'px;top:' + (textGroupBox.y + (/\?debug#?/.test(location.href)?this.textGroup.getItems().length * this.getlineHeight():0)) +
            'px;width:' + textGroupBox.width + 'px;height:' + textGroupBox.height + 'px;';

        return this;
    },
    updateTextOffsetData: function() {
        var me = this;

        var fontHeight = this.minderNode.getData('font-size') || this.minderNode.getStyle('font-size');
        var lineHeight = this.minderNode.getStyle('line-height') * fontHeight;

        var offsetHeight = (me.textGroup.getShapes().length * lineHeight - (lineHeight - fontHeight)) / 2;

        var box;
        this.textData = [];

        me.textGroup.eachItem(function(index,textShape){
            me.textData[index] = [];
            var currentLineTop = index * lineHeight + 1;
            var text = textShape.getContent();

            for (var i = 0, l = text.length; i < l; i++) {
                try {
                    box = textShape.getExtentOfChar(i);
                } catch (e) {
                    console.log(e);
                }
                me.textData[index].push({
                    x: box.x ,
                    y: currentLineTop - offsetHeight,
                    width: box.width,
                    height: box.height
                });
            }
            if(text.length === 0){
                me.textData[index].push({
                    x: 0,
                    y: currentLineTop - offsetHeight,
                    width: 0,
                    height:lineHeight
                });
            }
        });
        return this;
    },
    getlineHeight:function(){
        return this.minderNode.getStyle('line-height') * (this.minderNode.getData('font-size') || this.minderNode.getStyle('font-size'));
    },
    updateIndexByMouse : function(offset) {

        var me = this;
        //更新文本字符坐标
        me.updateTextOffsetData();

        this.index = 0;

        var lineHeight = this.getlineHeight();


        utils.each(this.textData,function(l,arr){

            var first = arr[0];

            //确定行号
            if(first.y <= offset.y && first.y + lineHeight >= offset.y){

                utils.each(arr,function(i,v){
                    //点击开始之前
                    if (i === 0 && offset.x <= v.x) {

                        return false;
                    }
                    if (offset.x >= v.x && offset.x <= v.x + v.width) {
                        if (offset.x - v.x > v.width / 2) {
                            me.index += i + 1;
                        } else {
                            me.index += i;
                        }
                        return false;
                    }
                    if (i == arr.length - 1 && offset.x >= v.x) {

                        me.index += (arr.length == 1 && arr[0].width === 0 ? 0 : arr.length);
                        return false;
                    }
                });
                return false;
            }else{

                me.index += arr.length + (arr.length == 1 && arr[0].width === 0 ? 0 : 1);
                return;
            }

        });

        this.selection.setStartOffset(this.index).collapse(true);

        return this;

    },
    setSelectionHeight: function() {

        this.selection.setHeight((this.minderNode.getData('font-size') || this.minderNode.getStyle('font-size')) * 1);
        return this;
    },
    updateSelectionByMousePosition: function(offset) {

        var me = this;
        var result = 0;
        var lineHeight =  this.getlineHeight();
        utils.each(this.textData,function(l,arr){
            var first = arr[0];
            //确定行号

            if(first.y <= offset.y && first.y  + lineHeight >= offset.y){

                utils.each(arr,function(i,v){
                    //点击开始之前
                    if (i === 0 && offset.x <= v.x) {
                        return false;
                    }
                    if (offset.x >= v.x && offset.x <= v.x + v.width) {

                        result += i;
                        if (offset.x - v.x > v.width / 2) {
                            result += 1;
                        }

                        return false;
                    }
                    if (i == arr.length - 1 && offset.x >= v.x) {

                        result += (arr.length == 1 && arr[0].width === 0 ? 0 : arr.length);

                        return false;
                    }
                });

                return false;
            }else{

                if(first.y > offset.y && l === 0){
                    result = 0;
                    return false;
                }else if(l == me.textData.length - 1 && first.y  + lineHeight < offset.y){
                    result += arr.length + 1;
                    return false;
                }
                result += arr.length + (arr.length == 1 && arr[0].width === 0 ? 0 : 1);
                return;
            }

        });
        if(result < me.index){
            this.selection.setStartOffset(result);
            this.selection.setEndOffset(me.index);

        }else if(result == me.index){

            this.selection.setStartOffset(result).collapse(true);
        }else{
            this.selection.setStartOffset(me.index);
            this.selection.setEndOffset(result);
        }
        return this;
    },

    updateRange: function() {
        this.range.update();
        return this;
    },
    updateContainerRangeBySel:function(){
        var me = this;
        this.range.setStartOffset(this.selection.startOffset);
        this.range.setEndOffset(this.selection.endOffset);

        if(browser.gecko){
            this.container.focus();
            setTimeout(function(){
                me.range.select();
            });
        }else{
            this.range.select();
        }
        return this;
    },
    updateSelectionByRange:function(){
        this.selection.setStartOffset(this.range.getStartOffset());
        this.selection.setEndOffset(this.range.getEndOffset());
        return this;
    },
    setIndex: function(index) {
        this.index = index;
        return this;
    },
    setContainerTxt: function(txt) {

        function encodeHtml(text) {
            return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        }

        if(txt){
            txt = encodeHtml(txt);
            txt = txt.replace(/[\n]/g,'<br\/>');
        }else{
            txt = '';
            this.textGroup.eachItem(function(i,item){
                txt += encodeHtml(item.getContent()) + '<br/>';
            });
        }
        this.container.innerHTML = txt;
        return this;
    },
    setReady:function(){
        this._ready = true;
    },
    clearReady:function(){
        this._ready = false;
    },
    isReady:function(){
        return this._ready;
    },
    focus:function(){
        this.container.focus();
    },
    getTxtOfContainer:function(){
        var result = '',cont = this.container;
        utils.each(cont.childNodes,function(i,n){
            if(n.nodeType == 3){
                result += n.nodeValue.replace(/[\u200b]/g, '');
            }else{
                if(n !== cont.lastChild)
                    result += '\n';
            }
        });
        return result;
    }
});
//模拟光标
Minder.Selection = kity.createClass( 'Selection', {
    base: kity.Path,
    constructor: function ( height, color, width ) {
        this.callBase();
        this.height = height || 20;
        this.setAttr('id','_kity_selection');
        this.width = 2;
        this.fill('rgb(27,171,255)');
        this.setHide();
        this.timer = null;
        this.collapsed = true;
        this.startOffset = this.endOffset = 0;
        this.setOpacity(0.5);
        this.setStyle('cursor','text');
        this._show = false;
        this.offset = [];
        this.setTranslate(-0.5, -1.5);
    },
    setMinderNode : function(node){
        this.minderNode = node;
    },
    setColor:function(color){

        this.fill(color);
    },
    updateOffsetByTextData:function(data,offset){
        if(this.collapsed){
            this.offset = utils.getValueByIndex(data,this.startOffset);
            return this;
        }else{
            var arrOffset = [],tmpOffset = {},
                startOffset = this.startOffset,
                endOffset = this.endOffset,
                cIndex = 0;

            utils.each(data,function(l,arr){
                tmpOffset = {
                    width:0,
                    x:0,
                    y:0
                };
                utils.each(arr,function(i,o){
                    if(cIndex >= startOffset && cIndex <= endOffset){
                        if(i === 0 || cIndex === startOffset){
                            tmpOffset.x = o.x;
                            tmpOffset.y = o.y;
                            tmpOffset.width =  o.width;
                            //i === 0 && offset && offset.x <= o.x && cIndex != startOffset ? 0 :
                        }else if(cIndex < endOffset){
                            tmpOffset.width += o.width;
                        }else if(cIndex === endOffset){
                            return false;
                        }

                    }
                    cIndex++;
                });
                if(tmpOffset.x !== undefined) {
                    arrOffset.push(tmpOffset);
                }
                if(cIndex === endOffset) {
                    return false;
                }
                if(arr.length == 1 && arr[0].width === 0)
                    return;

                cIndex++;

            });
            this.offset = arrOffset;
            return this;
        }
        this._show = true;
    },
    updatePosition: function(offset){
        var me = this;
        var r = Math.round;

        var rect = function (x, y, w, h) {
            return ['M', r(x), r(y),
                'h', r(w),
                'v', r(h),
                'h', -r(w),
                'v', -r(h),
                'z'];
        };

        offset = offset !== undefined ? offset : this.offset;

        if(this.collapsed){
            if (isNaN(offset.x) || isNaN(offset.y)) {
                if (console) console.warn('editor.selection.js 不正确的偏移位置');
                return this;
            }
            this.setPathData(rect(offset.x, offset.y, this.width, this.height));
        } else {
            this.setPathData(offset.reduce(function (prev, current) {
                return prev.concat(rect(current.x, current.y, current.width, me.height));
            }, []));
        }
        this._show = true;
        return this;
    },
    collapse : function(toStart){
        this.setOpacity(1);
        this.collapsed = true;
        if(toStart){
            this.endOffset = this.startOffset;

        }else{
            this.startOffset = this.endOffset;
        }
        return this;
    },
    setStartOffset:function(offset){
        this.startOffset = offset;
        if(this.startOffset >= this.endOffset){
            this.collapse(true);
            return this;
        }
        this.collapsed = false;
        this.setOpacity(0.5);
        return this;
    },
    setEndOffset:function(offset){
        this.endOffset = offset;
        if(this.endOffset <= this.startOffset){
           this.startOffset = offset;
           this.collapse(true);
            return this;
        }
        this.collapsed = false;
        this.setOpacity(0.5);
        return this;
    },
    update : function(data,offset){
        if(data){
            this.updateOffsetByTextData(data,offset);
        }
        this.updatePosition();
        this.setShow();
        return this;
    },
    setHeight: function ( height ) {
        this.height = Math.round(height) + 2;
        return this;
    },
    setHide: function () {
        clearInterval( this.timer );
        this.setStyle( 'display', 'none' );
        this._show = false;
        return this;
    },
    setHoldShow:function(){
        this.setStyle('display','');
        clearInterval(this.timer);
        return this;
    },
    setShow: function () {
        this.bringTop();
        clearInterval( this.timer );
        var me = this,
            state = '';

        me.setStyle( 'display', '' );
        me._show = true;
        if(this.collapsed){
            me.setOpacity(1);
            this.timer = setInterval( function () {
                me.setStyle( 'display', state );
                state = state ? '' : 'none';
            }, 400 );
        }
        return this;
    },
    setShowStatus:function(){
        this._show = true;
        return this;
    },
    isShow:function(){
        return this._show;
    },
    isHide:function(){

        return !this._show;
    }
} );
KityMinder.registerModule('basestylemodule', function() {
    var km = this;

    function getNodeDataOrStyle(node, name) {
        return node.getData(name) || node.getStyle(name);
    }

    KityMinder.TextRenderer.registerStyleHook(function(node, textGroup) {

        var fontWeight = getNodeDataOrStyle(node,'font-weight');
        var fontStyle = getNodeDataOrStyle(node, 'font-style');
        var styleHash = [fontWeight, fontStyle].join('/');

        textGroup.eachItem(function(index,item){
            item.setFont({
                'weight': fontWeight,
                'style': fontStyle
            });
        });

    });
    return {
        'commands': {
            'bold': kity.createClass('boldCommand', {
                base: Command,

                execute: function(km) {

                    var nodes = km.getSelectedNodes();
                    if (this.queryState('bold') == 1) {
                        utils.each(nodes, function(i, n) {
                            n.setData('font-weight').render();
                        });
                    } else {
                        utils.each(nodes, function(i, n) {
                            n.setData('font-weight', 'bold').render();
                        });
                    }
                    km.layout();
                },
                queryState: function() {
                    var nodes = km.getSelectedNodes(),
                        result = 0;
                    if (nodes.length === 0) {
                        return -1;
                    }
                    utils.each(nodes, function(i, n) {
                        if (n && n.getData('font-weight')) {
                            result = 1;
                            return false;
                        }
                    });
                    return result;
                }
            }),
            'italic': kity.createClass('italicCommand', {
                base: Command,

                execute: function(km) {

                    var nodes = km.getSelectedNodes();
                    if (this.queryState('italic') == 1) {
                        utils.each(nodes, function(i, n) {
                            n.setData('font-style').render();
                        });
                    } else {
                        utils.each(nodes, function(i, n) {
                            n.setData('font-style', 'italic').render();
                        });
                    }

                    km.layout();
                },
                queryState: function() {
                    var nodes = km.getSelectedNodes(),
                        result = 0;
                    if (nodes.length === 0) {
                        return -1;
                    }
                    utils.each(nodes, function(i, n) {
                        if (n && n.getData('font-style')) {
                            result = 1;
                            return false;
                        }
                    });
                    return result;
                }
            })
        },
        commandShortcutKeys: {
            'bold': 'ctrl+b', //bold
            'italic': 'ctrl+i' //italic
        }
    };
});
KityMinder.registerModule("fontmodule", function() {
    function getNodeDataOrStyle(node, name) {
        return node.getData(name) || node.getStyle(name);
    }

    KityMinder.TextRenderer.registerStyleHook(function(node, textGroup) {
        var dataColor = node.getData('color');
        var selectedColor = node.getStyle('selected-color');
        var styleColor = node.getStyle('color');

        var foreColor = dataColor || (node.isSelected() && selectedColor ? selectedColor : styleColor);
        var fontFamily = getNodeDataOrStyle(node, 'font-family');
        var fontSize = getNodeDataOrStyle(node, 'font-size');
        var fontHash = [fontFamily, fontSize].join('/');
        
        textGroup.fill(foreColor);
        node.setTmpData('fore-color', foreColor.toString());

        textGroup.eachItem(function(index,item){
            item.setFont({
                'family': fontFamily,
                'size': fontSize
            });
        });
        node.setTmpData('font-hash', fontHash);
    });

    return {
        defaultOptions: {
            'fontfamily': [{
                name: '宋体',
                val: '宋体,SimSun'
            }, {
                name: '微软雅黑',
                val: '微软雅黑,Microsoft YaHei'
            }, {
                name: '楷体',
                val: '楷体,楷体_GB2312,SimKai'
            }, {
                name: '黑体',
                val: '黑体, SimHei'
            }, {
                name: '隶书',
                val: '隶书, SimLi'
            }, {
                name: 'Andale Mono',
                val: 'andale mono'
            }, {
                name: 'Arial',
                val: 'arial,helvetica,sans-serif'
            }, {
                name: 'arialBlack',
                val: 'arial black,avant garde'
            }, {
                name: 'Comic Sans Ms',
                val: 'comic sans ms'
            }, {
                name: 'Impact',
                val: 'impact,chicago'
            }, {
                name: 'Times New Roman',
                val: 'times new roman'
            }, {
                name: 'Sans-Serif',
                val: 'sans-serif'
            }],
            'fontsize': [10, 12, 16, 18, 24, 32, 48]
        },
        "commands": {
            "forecolor": kity.createClass("fontcolorCommand", {
                base: Command,

                execute: function(km, color) {
                    var nodes = km.getSelectedNodes();
                    utils.each(nodes, function(i, n) {
                        n.setData('color', color);
                        n.render();
                    });
                },
                queryState: function(km) {
                    return km.getSelectedNodes().length == 0 ? -1 : 0
                },
                queryValue: function(km) {
                    if (km.getSelectedNodes().length == 1) {
                        return km.getSelectedNodes()[0].getData('color');
                    }
                    return 'mixed';
                }

            }),
            "background": kity.createClass("backgroudCommand", {
                base: Command,

                execute: function(km, color) {
                    var nodes = km.getSelectedNodes();
                    utils.each(nodes, function(i, n) {
                        n.setData('background', color);
                        n.render();
                    });
                },
                queryState: function(km) {
                    return km.getSelectedNodes().length == 0 ? -1 : 0
                },
                queryValue: function(km) {
                    if (km.getSelectedNodes().length == 1) {
                        return km.getSelectedNodes()[0].getData('background');
                    }
                    return 'mixed';
                }

            }),
            "fontfamily": kity.createClass("fontfamilyCommand", {
                base: Command,

                execute: function(km, family) {
                    var nodes = km.getSelectedNodes();
                    utils.each(nodes, function(i, n) {
                        n.setData('font-family', family);
                        n.render();
                        km.layout();
                    });
                },
                queryState: function(km) {
                    return km.getSelectedNodes().length === 0 ? -1 : 0
                },
                queryValue: function(km) {
                    var node = km.getSelectedNode();
                    if (node) return node.getData('font-family');
                    return null;
                }
            }),
            "fontsize": kity.createClass("fontsizeCommand", {
                base: Command,

                execute: function(km, size) {
                    var nodes = km.getSelectedNodes();
                    utils.each(nodes, function(i, n) {
                        n.setData('font-size', size);
                        n.render();
                        km.layout(300);
                    });
                },
                queryState: function(km) {
                    return km.getSelectedNodes().length == 0 ? -1 : 0
                },
                queryValue: function(km) {
                    var node = km.getSelectedNode();
                    if (node) return node.getData('font-size');
                    return null;
                }
            })
        }
    };
});
KityMinder.registerModule('Zoom', function() {
    var me = this;

    var timeline;

    me.setDefaultOptions('zoom', [10, 20, 30, 50, 80, 100, 120, 150, 200]);

    function setTextRendering() {
        var value = me._zoomValue >= 100 ? 'optimize-speed' : 'geometricPrecision';
        me.getRenderContainer().setAttr('text-rendering', value);
    }

    function fixPaperCTM(paper) {
        var node = paper.shapeNode;
        var ctm = node.getCTM();
        var matrix = new kity.Matrix(ctm.a, ctm.b, ctm.c, ctm.d, (ctm.e | 0) + 0.5, (ctm.f | 0) + 0.5);
        node.setAttribute('transform', 'matrix(' + matrix.toString() + ')');
    }

    kity.extendClass(Minder, {
        zoom: function(value) {
            var paper = this.getPaper();
            var viewport = paper.getViewPort();
            viewport.zoom = value / 100;
            viewport.center = {
                x: viewport.center.x,
                y: viewport.center.y
            };
            paper.setViewPort(viewport);
            if (value == 100) fixPaperCTM(paper);
        },
        getZoomValue: function() {
            return this._zoomValue;
        }
    });

    function zoomMinder(minder, value) {
        var paper = minder.getPaper();
        var viewport = paper.getViewPort();

        if (!value) return;

        setTextRendering();

        if (minder.getRoot().getComplex() > 200) {
            minder._zoomValue = value;
            minder.zoom(value);
            minder.fire('viewchange');
        } else {
            var animator = new kity.Animator({
                beginValue: minder._zoomValue,
                finishValue: value,
                setter: function(target, value) {
                    target.zoom(value);
                }
            });
            minder._zoomValue = value;
            if (timeline) {
                timeline.pause();
            }
            timeline = animator.start(minder, 300, 'easeInOutSine');
            timeline.on('finish', function() {
                minder.fire('viewchange');
            });
        }
        minder.fire('zoom', { zoom: value });
    }

    var ZoomCommand = kity.createClass('Zoom', {
        base: Command,
        execute: zoomMinder,
        queryValue: function(minder) {
            return minder._zoomValue;
        }
    });

    var ZoomInCommand = kity.createClass('ZoomInCommand', {
        base: Command,
        execute: function(minder) {
            zoomMinder(minder, this.nextValue(minder));
        },
        queryState: function(minder) {
            return +!this.nextValue(minder);
        },
        nextValue: function(minder) {
            var stack = minder.getOptions('zoom'),
                i;
            for (i = 0; i < stack.length; i++) {
                if (stack[i] > minder._zoomValue) return stack[i];
            }
            return 0;
        },
        enableReadOnly: true
    });

    var ZoomOutCommand = kity.createClass('ZoomOutCommand', {
        base: Command,
        execute: function(minder) {
            zoomMinder(minder, this.nextValue(minder));
        },
        queryState: function(minder) {
            return +!this.nextValue(minder);
        },
        nextValue: function(minder) {
            var stack = minder.getOptions('zoom'),
                i;
            for (i = stack.length - 1; i >= 0; i--) {
                if (stack[i] < minder._zoomValue) return stack[i];
            }
            return 0;
        },
        enableReadOnly: true
    });

    return {
        init: function() {
            this._zoomValue = 100;
            setTextRendering();
        },
        commands: {
            'zoom-in': ZoomInCommand,
            'zoom-out': ZoomOutCommand,
            'zoom': ZoomCommand
        },
        events: {
            'normal.mousewheel readonly.mousewheel': function(e) {
                if (!e.originEvent.ctrlKey && !e.originEvent.metaKey) return;

                var delta = e.originEvent.wheelDelta;
                var me = this;

                if (!kity.Browser.mac) {
                    delta = -delta;
                }

                // 稀释
                if (Math.abs(delta) > 100) {
                    clearTimeout(this._wheelZoomTimeout);
                } else {
                    return;
                }

                this._wheelZoomTimeout = setTimeout(function() {
                    var value;
                    var lastValue = me.getPaper()._zoom || 1;
                    if (delta < 0) {
                        me.execCommand('zoom-in');
                    } else if (delta > 0) {
                        me.execCommand('zoom-out');
                    }
                }, 100);

                e.originEvent.preventDefault();
            }
        },

        commandShortcutKeys: {
            'zoom-in': 'ctrl+=',
            'zoom-out': 'ctrl+-'
        }
    };
});
KityMinder.registerModule("hyperlink", function() {
    var linkShapePath = "M16.614,10.224h-1.278c-1.668,0-3.07-1.07-3.599-2.556h4.877c0.707,0,1.278-0.571,1.278-1.278V3.834 c0-0.707-0.571-1.278-1.278-1.278h-4.877C12.266,1.071,13.668,0,15.336,0h1.278c2.116,0,3.834,1.716,3.834,3.834V6.39 C20.448,8.508,18.73,10.224,16.614,10.224z M5.112,5.112c0-0.707,0.573-1.278,1.278-1.278h7.668c0.707,0,1.278,0.571,1.278,1.278 S14.765,6.39,14.058,6.39H6.39C5.685,6.39,5.112,5.819,5.112,5.112z M2.556,3.834V6.39c0,0.707,0.573,1.278,1.278,1.278h4.877 c-0.528,1.486-1.932,2.556-3.599,2.556H3.834C1.716,10.224,0,8.508,0,6.39V3.834C0,1.716,1.716,0,3.834,0h1.278 c1.667,0,3.071,1.071,3.599,2.556H3.834C3.129,2.556,2.556,3.127,2.556,3.834z";
    return {
        "commands": {
            "hyperlink": kity.createClass("hyperlink", {
                base: Command,

                execute: function(km, url, title) {
                    var nodes = km.getSelectedNodes();
                    utils.each(nodes, function(i, n) {
                        n.setData('hyperlink', url);
                        n.setData('hyperlinkTitle', title);
                        n.render();
                    });
                    km.layout();
                },
                queryState: function(km) {
                    var nodes = km.getSelectedNodes(),
                        result = 0;
                    if (nodes.length === 0) {
                        return -1;
                    }
                    utils.each(nodes, function(i, n) {
                        if (n && n.getData('hyperlink')) {
                            result = 0;
                            return false;
                        }
                    });
                    return result;
                },
                queryValue: function(km) {
                    var node = km.getSelectedNode();
                    return {
                        url: node.getData('hyperlink'),
                        title: node.getData('hyperlinkTitle')
                    };
                }
            }),
            "unhyperlink": kity.createClass("hyperlink", {
                base: Command,

                execute: function(km) {
                    var nodes = km.getSelectedNodes();
                    utils.each(nodes, function(i, n) {
                        n.setData('hyperlink');
                        n.render();
                    });
                    km.layout();
                },
                queryState: function(km) {
                    var nodes = km.getSelectedNodes();

                    if (nodes.length === 0) {
                        return -1;
                    }
                    var link = false;
                    utils.each(nodes, function(i, n) {
                        if (n.getData('hyperlink')) {
                            link = true;
                            return false;
                        }
                    });
                    if (link) {
                        return 0;
                    }
                    return -1;
                }
            })
        },
        'renderers': {
            right: kity.createClass('hyperlinkrender', {
                base: KityMinder.Renderer,

                create: function() {

                    var link = new kity.HyperLink();
                    var linkshape = new kity.Path();
                    var outline = new kity.Rect(24, 22, -2, -6, 4).fill('rgba(255, 255, 255, 0)');


                    linkshape.setPathData(linkShapePath).fill('#666');
                    link.addShape(outline);
                    link.addShape(linkshape);
                    link.setTarget('_blank');
                    link.setStyle('cursor', 'pointer');

                    link.on('mouseover', function() {
                        outline.fill('rgba(255, 255, 200, .8)');
                    }).on('mouseout', function() {
                        outline.fill('rgba(255, 255, 255, 0)');
                    });
                    return link;
                },

                shouldRender: function(node) {
                    return node.getData('hyperlink');
                },

                update: function(link, node, box) {

                    var href = node.getData('hyperlink');
                    link.setHref(href);
                    var title = node.getData('hyperlinkTitle');

                    if (title) {
                        title = [title, '(', href, ')'].join('');
                    } else {
                        title = href;
                    }

                    link.node.setAttributeNS('http://www.w3.org/1999/xlink', 'title', title);

                    var spaceRight = node.getStyle('space-right');

                    link.setTranslate(box.right + spaceRight + 2, -5);
                    return new kity.Box({
                        x: box.right + spaceRight,
                        y: -11,
                        width: 24,
                        height: 22
                    });
                }
            })
        }

    };
});
kity.extendClass(MinderNode, {
    arrange: function(index) {
        var parent = this.parent;
        if (!parent) return;
        var sibling = parent.children;

        if (index < 0 || index >= sibling.length) return;
        sibling.splice(this.getIndex(), 1);
        sibling.splice(index, 0, this);
        return this;
    }
});

function asc(nodeA, nodeB) {
    return nodeA.getIndex() - nodeB.getIndex();
}
function desc(nodeA, nodeB) {
    return -asc(nodeA, nodeB);
}

function canArrange(km) {
    var selected = km.getSelectedNode();
    return selected && selected.parent && selected.parent.children.length > 1;
}

var ArrangeUpCommand = kity.createClass('ArrangeUpCommand', {
    base: Command,

    execute: function(km) {
        var nodes = km.getSelectedNodes();
        nodes.sort(asc);
        var lastIndexes = nodes.map(function(node) {
            return node.getIndex();
        });
        nodes.forEach(function(node, index) {
            node.arrange(lastIndexes[index] - 1);
        });
        km.layout(300);
    },

    queryState: function(km) {
        var selected = km.getSelectedNode();
        return selected ? 0 : -1;
    }
});

var ArrangeDownCommand = kity.createClass('ArrangeUpCommand', {
    base: Command,

    execute: function(km) {
        var nodes = km.getSelectedNodes();
        nodes.sort(desc);
        var lastIndexes = nodes.map(function(node) {
            return node.getIndex();
        });
        nodes.forEach(function(node, index) {
            node.arrange(lastIndexes[index] + 1);
        });
        km.layout(300);
    },

    queryState: function(km) {
        var selected = km.getSelectedNode();
        return selected ? 0 : -1;
    }
});

var ArrangeCommand = kity.createClass('ArrangeCommand', {
    base: Command,

    execute: function(km, nodes, index) {
        nodes = nodes && nodes.slice() || km.getSelectedNodes().slice();

        if (!nodes.length) return;

        var ancestor = MinderNode.getCommonAncestor(nodes);

        if (ancestor != nodes[0].parent) return;

        var indexed = nodes.map(function(node) {
            return {
                index: node.getIndex(),
                node: node
            };
        });

        var asc = Math.min.apply(Math, indexed.map(function(one) { return one.index; })) >= index;

        indexed.sort(function(a, b) {
            return asc ? (b.index - a.index) : (a.index - b.index);
        });

        indexed.forEach(function(one) {
            one.node.arrange(index);
        });

        km.layout(300);
    },

    queryState: function(km) {
        var selected = km.getSelectedNode();
        return selected ? 0 : -1;
    }
});

KityMinder.registerModule('ArrangeModule', {
    commands: {
        'arrangeup': ArrangeUpCommand,
        'arrangedown': ArrangeDownCommand,
        'arrange': ArrangeCommand
    },
    contextmenu: [{
        command: 'arrangeup'
    }, {
        command: 'arrangedown'
    }, {
        divider: true
    }],
    commandShortcutKeys: {
        'arrangeup': 'normal::alt+Up',
        'arrangedown': 'normal::alt+Down'
    }
});
KityMinder.registerModule( "ClipboardModule", function () {
    var km = this,

        _clipboardNodes = [],

        _selectedNodes = [];

    function appendChildNode(parent, child) {
        _selectedNodes.push(child);
        km.appendNode(child, parent);
        child.render();
        child.setLayoutOffset(null);
        var children = utils.cloneArr(child.children);
        for (var i = 0, ci; (ci = children[i]); i++) {
            appendChildNode(child, ci);
        }
    }

    function sendToClipboard(nodes) {
        if (!nodes.length) return;
        nodes.sort(function(a, b) {
            return a.getIndex() - b.getIndex();
        });
        _clipboardNodes = nodes.map(function(node) {
            return node.clone();
        });
    }

    var CopyCommand = kity.createClass('CopyCommand', {
        base: Command,

        execute: function(km) {
            sendToClipboard(km.getSelectedAncestors(true));
            this.setContentChanged(false);
        }
    });

    var CutCommand = kity.createClass('CutCommand', {
        base: Command,

        execute: function(km) {
            var ancestors = km.getSelectedAncestors();

            if (ancestors.length === 0) return;

            sendToClipboard(ancestors);

            km.select(MinderNode.getCommonAncestor(ancestors), true);

            ancestors.slice().forEach(function(node) {
                km.removeNode(node);
            });

            km.layout(300);
        }
    });

    var PasteCommand = kity.createClass('PasteCommand', {
        base: Command,

        execute: function(km) {
            if (_clipboardNodes.length) {
                var node = km.getSelectedNode();
                if (!node) return;

                for (var i = 0, ni; (ni = _clipboardNodes[i]); i++) {
                    appendChildNode(node, ni.clone());
                }

                km.select(_selectedNodes, true);
                _selectedNodes = [];

                km.layout(300);
            }
        },

        queryState: function(km) {
            return km.getSelectedNode() ? 0 : -1;
        }
    });

    return {
        'commands': {
            'copy': CopyCommand,
            'cut': CutCommand,
            'paste': PasteCommand
        },
        'commandShortcutKeys': {
            'copy': 'normal::ctrl+c|',
            'cut': 'normal::ctrl+x',
            'paste': 'normal::ctrl+v'
        }
    };
} );
KityMinder.registerModule('StyleModule', function() {
    var styleNames = ['font-size', 'font-family', 'font-weight', 'font-style', 'background', 'color'];
    var styleClipBoard = null;

    function hasStyle(node) {
        var data = node.getData();
        for(var i = 0; i < styleNames.length; i++) {
            if (styleNames[i] in data) return true;
        }
    }

    return {
        "commands": {
            "copystyle": kity.createClass("CopyStyleCommand", {
                base: Command,

                execute: function(minder) {
                    var node = minder.getSelectedNode();
                    var nodeData = node.getData();
                    styleClipBoard = {};
                    styleNames.forEach(function(name) {
                        if (name in nodeData) styleClipBoard[name] = nodeData[name];
                        else {
                            styleClipBoard[name] = null;
                            delete styleClipBoard[name];
                        }
                    });
                    return styleClipBoard;
                },

                queryState: function(minder) {
                    var nodes = minder.getSelectedNodes();
                    if (nodes.length !== 1) return -1;
                    return hasStyle(nodes[0]) ? 0 : -1;
                }
            }),

            "pastestyle": kity.createClass("PastStyleCommand", {
                base: Command,

                execute: function(minder) {
                    minder.getSelectedNodes().forEach(function(node) {
                        for (var name in styleClipBoard) {
                            if (styleClipBoard.hasOwnProperty(name))
                                node.setData(name, styleClipBoard[name]);
                        }
                    });
                    minder.renderNodeBatch(minder.getSelectedNodes());
                    minder.layout(300);
                    return styleClipBoard;
                },

                queryState: function(minder) {
                    return (styleClipBoard && minder.getSelectedNodes().length) ? 0 : -1;
                }
            }),

            "clearstyle": kity.createClass("ClearStyleCommand", {
                base: Command,
                execute: function(minder) {
                    minder.getSelectedNodes().forEach(function(node) {
                        styleNames.forEach(function(name) {
                            node.setData(name);
                        });
                    });
                    minder.renderNodeBatch(minder.getSelectedNodes());
                    minder.layout(300);
                    return styleClipBoard;
                },

                queryState: function(minder) {
                    var nodes = minder.getSelectedNodes();
                    if (!nodes.length) return -1;
                    for(var i = 0; i < nodes.length; i++) {
                        if (hasStyle(nodes[i])) return 0;
                    }
                    return -1;
                }
            })
        }
    };
});
/* global zip:true */
/*
    http://www.xmind.net/developer/
    Parsing XMind file
    XMind files are generated in XMind Workbook (.xmind) format, an open format
    that is based on the principles of OpenDocument. It consists of a ZIP
    compressed archive containing separate XML documents for content and styles,
    a .jpg image file for thumbnails, and directories for related attachments.
 */
KityMinder.registerProtocol('xmind', function(minder) {

    // 标签 map
    var markerMap = {
        'priority-1': ['priority', 1],
        'priority-2': ['priority', 2],
        'priority-3': ['priority', 3],
        'priority-4': ['priority', 4],
        'priority-5': ['priority', 5],
        'priority-6': ['priority', 6],
        'priority-7': ['priority', 7],
        'priority-8': ['priority', 8],

        'task-start': ['progress', 1],
        'task-oct': ['progress', 2],
        'task-quarter': ['progress', 3],
        'task-3oct': ['progress', 4],
        'task-half': ['progress', 5],
        'task-5oct': ['progress', 6],
        'task-3quar': ['progress', 7],
        'task-7oct': ['progress', 8],
        'task-done': ['progress', 9]
    };

    return {
        fileDescription: 'XMind 格式',
        fileExtension: '.xmind',
        dataType: 'blob',
        mineType: 'application/octet-stream',

        decode: function(local) {

            function processTopic(topic, obj) {

                //处理文本
                obj.data = {
                    text: topic.title
                };

                // 处理标签
                if (topic.marker_refs && topic.marker_refs.marker_ref) {
                    var markers = topic.marker_refs.marker_ref;
                    var type;
                    if (markers.length && markers.length > 0) {
                        for (var i in markers) {
                            type = markerMap[markers[i].marker_id];
                            if (type) obj.data[type[0]] = type[1];
                        }
                    } else {
                        type = markerMap[markers.marker_id];
                        if (type) obj.data[type[0]] = type[1];
                    }
                }

                // 处理超链接
                if (topic['xlink:href']) {
                    obj.data.hyperlink = topic['xlink:href'];
                }
                //处理子节点
                var topics = topic.children && topic.children.topics;
                var subTopics = topics && (topics.topic || topics[0] && topics[0].topic);
                if (subTopics) {
                    var tmp = subTopics;
                    if (tmp.length && tmp.length > 0) { //多个子节点
                        obj.children = [];

                        for (var i in tmp) {
                            obj.children.push({});
                            processTopic(tmp[i], obj.children[i]);
                        }

                    } else { //一个子节点
                        obj.children = [{}];
                        processTopic(tmp, obj.children[0]);
                    }
                }
            }

            function xml2km(xml) {
                var json = $.xml2json(xml);
                var result = {};
                var sheet = json.sheet;
                var topic = utils.isArray(sheet) ? sheet[0].topic : sheet.topic;
                processTopic(topic, result);
                return result;
            }

            function getEntries(file, onend) {
                return new Promise(function(resolve, reject) {                    
                    zip.createReader(new zip.BlobReader(file), function(zipReader) {
                        zipReader.getEntries(resolve);
                    }, reject);
                });
            }

            function readDocument(entries) {
                return new Promise(function(resolve, reject) {
                    var entry, json;

                    // 查找文档入口
                    while ((entry = entries.pop())) {

                        if (entry.filename.split('/').pop() == 'content.xml') break;

                        entry = null;

                    }

                    // 找到了读取数据
                    if (entry) {

                        entry.getData(new zip.TextWriter(), function(text) {
                            try {
                                json = xml2km($.parseXML(text));
                                resolve(json);
                            } catch (e) {
                                reject(e);
                            }
                        });

                    } 

                    // 找不到返回失败
                    else {
                        reject(new Error('Content document missing'));
                    }
                });
            }

            return getEntries(local).then(readDocument);

        },

        encode: function(json, km, options) {
            var url = 'native-support/export.php';
            var data = JSON.stringify(json);

            function fetch() {
                return new Promise(function(resolve, reject) {

                    var xhr = new XMLHttpRequest();
                    xhr.open('POST', url);

                    xhr.responseType = 'blob';
                    xhr.onload = resolve;
                    xhr.onerror = reject;

                    var form = new FormData();
                    form.append('type', 'xmind');
                    form.append('data', data);

                    xhr.send(form);

                }).then(function(e) {
                    return e.target.response;
                });
            }

            function download() {
                var filename = options.filename || 'xmind.xmind';

                var form = document.createElement('form');
                form.setAttribute('action', url);
                form.setAttribute('method', 'POST');
                form.appendChild(field('filename', filename));
                form.appendChild(field('type', 'xmind'));
                form.appendChild(field('data', data));
                form.appendChild(field('download', '1'));
                document.body.appendChild(form);
                form.submit();
                document.body.removeChild(form);

                function field(name, content) {
                    var input = document.createElement('input');
                    input.type = 'hidden';
                    input.name = name;
                    input.value = content;
                    return input;
                }
            }

            if (options && options.download) {
                return download();
            } else {
                return fetch();
            }
        },

        // recognize: recognize,
        recognizePriority: -1
    };

});
/**
 * @fileOverview FreeMind 文件格式支持
 *
 * Freemind 文件后缀为 .mm，实际上是一个 XML 文件
 * @see http://freemind.sourceforge.net/
 */

KityMinder.registerProtocol('freemind', function(minder) {

    // 标签 map
    var markerMap = {
        'full-1': ['priority', 1],
        'full-2': ['priority', 2],
        'full-3': ['priority', 3],
        'full-4': ['priority', 4],
        'full-5': ['priority', 5],
        'full-6': ['priority', 6],
        'full-7': ['priority', 7],
        'full-8': ['priority', 8]
    };

    function processTopic(topic, obj) {

        //处理文本
        obj.data = {
            text: topic.TEXT
        };
        var i;

        // 处理标签
        if (topic.icon) {
            var icons = topic.icon;
            var type;
            if (icons.length && icons.length > 0) {
                for (i in icons) {
                    type = markerMap[icons[i].BUILTIN];
                    if (type) obj.data[type[0]] = type[1];
                }
            } else {
                type = markerMap[icons.BUILTIN];
                if (type) obj.data[type[0]] = type[1];
            }
        }

        // 处理超链接
        if (topic.LINK) {
            obj.data.hyperlink = topic.LINK;
        }

        //处理子节点
        if (topic.node) {

            var tmp = topic.node;
            if (tmp.length && tmp.length > 0) { //多个子节点
                obj.children = [];

                for (i in tmp) {
                    obj.children.push({});
                    processTopic(tmp[i], obj.children[i]);
                }

            } else { //一个子节点
                obj.children = [{}];
                processTopic(tmp, obj.children[0]);
            }
        }
    }

    function xml2km(xml) {
        var json = $.xml2json(xml);
        var result = {};
        processTopic(json.node, result);
        return result;
    }

    return {
        fileDescription: 'Freemind 格式',
        fileExtension: '.mm',
        dataType: 'text',

        decode: function(local) {
            return new Promise(function(resolve, reject) {
                try {
                    resolve(xml2km(local));
                } catch (e) {
                    reject(new Error('XML 文件损坏！'));
                }
            });
        },

        encode: function(json, km, options) {
            var url = 'native-support/export.php';
            var data = JSON.stringify(json);

            function fetch() {
                return new Promise(function(resolve, reject) {
                    var xhr = new XMLHttpRequest();
                    xhr.open('POST', url);
                    
                    xhr.responseType = 'blob';
                    xhr.onload = resolve;
                    xhr.onerror = reject;

                    var form = new FormData();
                    form.append('type', 'freemind');
                    form.append('data', data);

                    xhr.send(form);
                }).then(function(e) {
                    return e.target.response;
                });
            }

            function download() {
                var filename = options.filename || 'freemind.mm';

                var form = document.createElement('form');
                form.setAttribute('action', url);
                form.setAttribute('method', 'POST');
                form.appendChild(field('filename', filename));
                form.appendChild(field('type', 'freemind'));
                form.appendChild(field('data', data));
                form.appendChild(field('download', '1'));
                document.body.appendChild(form);
                form.submit();
                document.body.removeChild(form);

                function field(name, content) {
                    var input = document.createElement('input');
                    input.type = 'hidden';
                    input.name = name;
                    input.value = content;
                    return input;
                }
            }

            if (options && options.download) {
                return download();
            } else {
                return fetch();
            }
        }
    };

});
/* global zip:true */
/*
    http://www.mindjet.com/mindmanager/
    mindmanager的后缀为.mmap，实际文件格式是zip，解压之后核心文件是Document.xml
*/
KityMinder.registerProtocol('mindmanager', function(minder) {

    // 标签 map
    var markerMap = {
        'urn:mindjet:Prio1': ['PriorityIcon', 1],
        'urn:mindjet:Prio2': ['PriorityIcon', 2],
        'urn:mindjet:Prio3': ['PriorityIcon', 3],
        'urn:mindjet:Prio4': ['PriorityIcon', 4],
        'urn:mindjet:Prio5': ['PriorityIcon', 5],
        '0': ['ProgressIcon', 1],
        '25': ['ProgressIcon', 2],
        '50': ['ProgressIcon', 3],
        '75': ['ProgressIcon', 4],
        '100': ['ProgressIcon', 5]
    };

    function processTopic(topic, obj) {
        //处理文本
        obj.data = {
            text: topic.Text && topic.Text.PlainText || ''
        }; // 节点默认的文本，没有Text属性

        // 处理标签
        if (topic.Task) {

            var type;
            if (topic.Task.TaskPriority) {
                type = markerMap[topic.Task.TaskPriority];
                if (type) obj.data[type[0]] = type[1];
            }

            if (topic.Task.TaskPercentage) {
                type = markerMap[topic.Task.TaskPercentage];
                if (type) obj.data[type[0]] = type[1];
            }
        }

        // 处理超链接
        if (topic.Hyperlink) {
            obj.data.hyperlink = topic.Hyperlink.Url;
        }

        //处理子节点
        if (topic.SubTopics && topic.SubTopics.Topic) {

            var tmp = topic.SubTopics.Topic;
            if (tmp.length && tmp.length > 0) { //多个子节点
                obj.children = [];

                for (var i in tmp) {
                    obj.children.push({});
                    processTopic(tmp[i], obj.children[i]);
                }

            } else { //一个子节点
                obj.children = [{}];
                processTopic(tmp, obj.children[0]);
            }
        }
    }

    function xml2km(xml) {
        var json = $.xml2json(xml);
        var result = {};
        processTopic(json.OneTopic.Topic, result);
        return result;
    }

    function getEntries(file) {
        return new Promise(function(resolve, reject) {
            zip.createReader(new zip.BlobReader(file), function(zipReader) {
                zipReader.getEntries(resolve);
            }, reject);
        });
    }

    function readMainDocument(entries) {

        return new Promise(function(resolve, reject) {

            var entry, json;

            // 查找文档入口
            while ((entry = entries.pop())) {

                if (entry.filename.split('/').pop() == 'Document.xml') break;

                entry = null;

            }

            // 找到了读取数据
            if (entry) {

                entry.getData(new zip.TextWriter(), function(text) {
                    json = xml2km($.parseXML(text));
                    resolve(json);
                });

            } 

            // 找不到返回失败
            else {
                reject(new Error('Main document missing'));
            }

        });
    }

    return {
        fileDescription: 'MindManager 格式',
        fileExtension: '.mmap',
        dataType: 'blob',

        decode: function(local) {
            return getEntries(local).then(readMainDocument);
        },

        // 暂时不支持编码
        encode: null,

        recognizePriority: -1
    };
});
KityMinder.registerProtocol('plain', function(minder) {
    var LINE_ENDING = '\r',
        LINE_ENDING_SPLITER = /\r\n|\r|\n/,
        TAB_CHAR = '\t';

    function repeat(s, n) {
        var result = '';
        while (n--) result += s;
        return result;
    }

    function encode(json, level) {
        var local = '';
        level = level || 0;
        local += repeat(TAB_CHAR, level);
        local += json.data.text + LINE_ENDING;
        if (json.children) {
            json.children.forEach(function(child) {
                local += encode(child, level + 1);
            });
        }
        return local;
    }

    function isEmpty(line) {
        return !/\S/.test(line);
    }

    function getLevel(line) {
        var level = 0;
        while (line.charAt(level) === TAB_CHAR) level++;
        return level;
    }

    function getNode(line) {
        return {
            data: {
                text: line.replace(new RegExp('^' + TAB_CHAR + '*'), '')
            }
        };
    }

    /**
     * 文本解码
     *
     * @param {string} local 文本内容
     * @param {=boolean} root 自动根节点
     * @return {Object} 返回解析后节点
     */
    function decode(local, root) {
        var json,
            offset,
            parentMap = {},
            lines = local.split(LINE_ENDING_SPLITER),
            line, level, node;

        function addChild(parent, child) {
            var children = parent.children || (parent.children = []);
            children.push(child);
        }
        if (root) {
            parentMap[0] = json = getNode('root');
            offset = 1;
        } else {
            offset = 0;
        }

        for (var i = 0; i < lines.length; i++) {
            line = lines[i];
            if (isEmpty(line)) continue;

            level = getLevel(line) + offset;
            node = getNode(line);

            if (level === 0) {
                if (json) {
                    throw new Error('Invalid local format');
                }
                json = node;
            } else {
                if (!parentMap[level - 1]) {
                    throw new Error('Invalid local format');
                }
                addChild(parentMap[level - 1], node);
            }
            parentMap[level] = node;
        }
        return json;
    }

    return {
        fileDescription: '大纲文本',
        fileExtension: '.txt',
        mineType: 'text/plain',
        dataType: 'text',

        encode: function(json) {
            return encode(json, 0);
        },

        decode: function(local, root) {
            return decode(local, root);
        },

        recognizePriority: -1
    };
});

/**
 * @fileOverview
 *
 * Markdown 格式导入导出支持
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerProtocol('markdown', function() {
    var LINE_ENDING_SPLITER = /\r\n|\r|\n/;
    var EMPTY_LINE = '';
    var NOTE_MARK_START = '<!--Note-->';
    var NOTE_MARK_CLOSE = '<!--/Note-->';

    function encode(json) {

        return _build(json, 1).join('\n');
    }

    function _build(node, level) {
        var lines = [];

        level = level || 1;

        var sharps = _generateHeaderSharp(level);
        lines.push(sharps + ' ' + node.data.text);
        lines.push(EMPTY_LINE);
        
        var note = node.data.note;
        if (note) {
            var hasSharp = /^#/.test(note);
            if (hasSharp) {
                lines.push(NOTE_MARK_START);
                note = note.replace(/^#+/gm, function($0) {
                    return sharps + $0;
                });
            }
            lines.push(note);
            if (hasSharp) {
                lines.push(NOTE_MARK_CLOSE);
            }
            lines.push(EMPTY_LINE);
        }

        if (node.children) node.children.forEach(function(child) {
            lines = lines.concat(_build(child, level + 1));
        });

        return lines;
    }

    function _generateHeaderSharp(level) {
        var sharps = '';
        while(level--) sharps += '#';
        return sharps;
    }

    function decode(markdown) {

        var json,
            parentMap = {},
            lines, line, lineInfo, level, node, parent, noteProgress, codeBlock;

        // 一级标题转换 `{title}\n===` => `# {title}`
        markdown = markdown.replace(/^(.+)\n={3,}/, function($0, $1) {
            return '# ' + $1;
        });

        lines = markdown.split(LINE_ENDING_SPLITER);

        // 按行分析
        for (var i = 0; i < lines.length; i++) {
            line = lines[i];

            lineInfo = _resolveLine(line);

            // 备注标记处理
            if (lineInfo.noteClose) {
                noteProgress = false;
                continue;
            } else if (lineInfo.noteStart) {
                noteProgress = true;
                continue;
            }

            // 代码块处理
            codeBlock = lineInfo.codeBlock ? !codeBlock : codeBlock;

            // 备注条件：备注标签中，非标题定义，或标题越位
            if (noteProgress || codeBlock || !lineInfo.level || lineInfo.level > level + 1) {
                if (node) _pushNote(node, line);
                continue;
            }

            // 标题处理
            level = lineInfo.level;
            node = _initNode(lineInfo.content, parentMap[level - 1]);
            parentMap[level] = node;
        }

        _cleanUp(parentMap[1]);
        return parentMap[1];
    }

    function _initNode(text, parent) {
        var node = {
            data: {
                text: text,
                note: ''
            }
        };
        if (parent) {
            if (parent.children) parent.children.push(node);
            else parent.children = [node];
        }
        return node;
    }

    function _pushNote(node, line) {
        node.data.note += line + '\n';
    }

    function _isEmpty(line) {
        return !/\S/.test(line);
    }

    function _resolveLine(line) {
        var match = /^(#+)?\s*(.*)$/.exec(line);
        return {
            level: match[1] && match[1].length || null,
            content: match[2],
            noteStart: line == NOTE_MARK_START,
            noteClose: line == NOTE_MARK_CLOSE,
            codeBlock: /^\s*```/.test(line)
        };
    }

    function _cleanUp(node) {
        if (!/\S/.test(node.data.note)) {
            node.data.note = null;
            delete node.data.note;
        } else {
            var notes = node.data.note.split('\n');
            while(notes.length && !/\S/.test(notes[0])) notes.shift();
            while(notes.length && !/\S/.test(notes[notes.length - 1])) notes.pop();
            node.data.note = notes.join('\n');
        }
        if (node.children) node.children.forEach(_cleanUp);
    }

    return {
        fileDescription: 'Markdown/GFM 格式',
        fileExtension: '.md',
        mineType: 'text/markdown',
        dataType: 'text',

        encode: function(json) {
            return encode(json);
        },

        decode: function(markdown) {
            return decode(markdown);
        },

        recognizePriority: -1
    };
});
KityMinder.registerProtocol('json', function(minder) {

    return {
        fileDescription: 'KityMinder 格式',
        fileExtension: '.km',
        dataType: 'text',
        mineType: 'application/json',

        encode: function(json) {
            return JSON.stringify(json);
        },

        decode: function(local) {
            return JSON.parse(local);
        }
    };
});
if (!kity.Browser.ie) {

    KityMinder.registerProtocol('png', function(minder) {

        var DomURL = window.URL || window.webkitURL || window;

        function loadImage(url, callback) {
            return new Promise(function(resolve, reject) {
                var image = document.createElement('img');
                image.onload = function() {
                    resolve(this);
                };
                image.onerror = function(err) {
                    reject(err);
                };
                image.crossOrigin = '';
                image.src = url;
            });
        }

        function getSVGInfo() {
            var paper = minder.getPaper(),
                paperTransform,
                domContainer = paper.container,
                svgXml,
                $svg,

                renderContainer = minder.getRenderContainer(),
                renderBox = renderContainer.getRenderBox(),
                width = renderBox.width + 1,
                height = renderBox.height + 1,

                blob, svgUrl, img;

            // 保存原始变换，并且移动到合适的位置
            paperTransform = paper.shapeNode.getAttribute('transform');
            paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)');
            renderContainer.translate(-renderBox.x, -renderBox.y);

            // 获取当前的 XML 代码
            svgXml = paper.container.innerHTML;

            // 回复原始变换及位置
            renderContainer.translate(renderBox.x, renderBox.y);
            paper.shapeNode.setAttribute('transform', paperTransform);

            // 过滤内容
            $svg = $(svgXml).filter('svg');
            $svg.attr({
                width: renderBox.width + 1,
                height: renderBox.height + 1,
                style: 'font-family: Arial, "Microsoft Yahei","Heiti SC";'
            });

            svgXml = $('<div></div>').append($svg).html();

            // Dummy IE
            svgXml = svgXml.replace(' xmlns="http://www.w3.org/2000/svg" xmlns:NS1="" NS1:ns1:xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:NS2="" NS2:xmlns:ns1=""', '');

            // svg 含有 &nbsp; 符号导出报错 Entity 'nbsp' not defined
            svgXml = svgXml.replace(/&nbsp;/g, '&#xa0;');

            blob = new Blob([svgXml], {
                type: 'image/svg+xml'
            });

            svgUrl = DomURL.createObjectURL(blob);

            //svgUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgXml);

            return {
                width: width,
                height: height,
                dataUrl: svgUrl,
                xml: svgXml
            };
        }

        function encode(json) {

            /* 绘制 PNG 的画布及上下文 */
            var canvas = document.createElement('canvas');
            var ctx = canvas.getContext('2d');

            /* 尝试获取背景图片 URL 或背景颜色 */
            var bgDeclare = minder.getStyle('background').toString();
            var bgUrl = /url\((.+)\)/.exec(bgDeclare);
            var bgColor = kity.Color.parse(bgDeclare);

            /* 获取 SVG 文件内容 */
            var svgInfo = getSVGInfo();
            var width = svgInfo.width;
            var height = svgInfo.height;
            var svgDataUrl = svgInfo.dataUrl;

            /* 画布的填充大小 */
            var padding = 20;

            canvas.width = width + padding * 2;
            canvas.height = height + padding * 2;

            function fillBackground(ctx, style) {
                ctx.save();
                ctx.fillStyle = style;
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.restore();
            }

            function drawImage(ctx, image, x, y) {
                ctx.drawImage(image, x, y);
            }

            function generateDataUrl(canvas) {
                try {
                    var url = canvas.toDataURL('png');
                    return url;
                } catch (e) {
                    throw new Error('当前浏览器版本不支持导出 PNG 功能，请尝试升级到最新版本！');
                }
            }

            function drawSVG() {
                if (typeof(window.canvg) != 'undefined') {
                    return new Promise(function(resolve) {
                        window.canvg(canvas, svgInfo.xml, {
                            ignoreMouse: true, 
                            ignoreAnimation: true, 
                            ignoreDimensions: true, 
                            ignoreClear: true, 
                            offsetX: padding, 
                            offsetY: padding,
                            renderCallback: function() {
                                resolve(generateDataUrl(canvas));
                            } 
                        });
                    });
                } else {
                    return loadImage(svgDataUrl).then(function(svgImage) {
                        drawImage(ctx, svgImage, padding, padding);
                        DomURL.revokeObjectURL(svgDataUrl);
                        return generateDataUrl(canvas);
                    });
                }
            }

            if (bgUrl) {

                return loadImage(bgUrl[1]).then(function(image) {

                    fillBackground(ctx, ctx.createPattern(image, 'repeat'));

                    return drawSVG();

                });

            } else {

                fillBackground(ctx, bgColor.toString());
                return drawSVG();

            }
        }

        return {
            fileDescription: 'PNG 图片',
            fileExtension: '.png',
            mineType: 'image/png',
            dataType: 'base64',
            encode: encode,
            recognizePriority: -1
        };
    });
}
if (!kity.Browser.ie) {

    KityMinder.registerProtocol('svg', function(minder) {

        return {
            fileDescription: 'SVG 矢量图',
            fileExtension: '.svg',
            mineType: 'image/svg+xml',
            dataType: 'text',

            encode: function(json) {

                var paper = minder.getPaper(),
                    paperTransform = paper.shapeNode.getAttribute('transform'),
                    svgXml,
                    $svg,

                    renderContainer = minder.getRenderContainer(),
                    renderBox = renderContainer.getRenderBox(),
                    transform = renderContainer.getTransform(),
                    width = renderBox.width,
                    height = renderBox.height,
                    padding = 20;

                paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)');
                svgXml = paper.container.innerHTML;
                paper.shapeNode.setAttribute('transform', paperTransform);

                $svg = $(svgXml).filter('svg');
                $svg.attr({
                    width: width + padding * 2 | 0,
                    height: height + padding * 2 | 0,
                    style: 'font-family: Arial, "Microsoft Yahei",  "Heiti SC"; background: ' + minder.getStyle('background')
                });
                $svg[0].setAttribute('viewBox', [renderBox.x - padding | 0,
                    renderBox.y - padding | 0,
                    width + padding * 2 | 0,
                    height + padding * 2 | 0
                ].join(' '));

                // need a xml with width and height
                svgXml = $('<div></div>').append($svg).html();

                svgXml = $('<div></div>').append($svg).html();

                // svg 含有 &nbsp; 符号导出报错 Entity 'nbsp' not defined
                svgXml = svgXml.replace(/&nbsp;/g, '&#xa0;');

                // svg 含有 &nbsp; 符号导出报错 Entity 'nbsp' not defined
                return svgXml;
            },

            recognizePriority: -1
        };
    });
}
/**
 * @fileOverview
 *
 * KityMinder UI 注册及加载机制
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

(function() {
    var uiQueue = [];

    /* 注册一个新的 UI 交互 */
    KityMinder.registerUI = function(id, deps, ui) {
        if (typeof(deps) == 'function') {
            ui = deps;
            deps = null;
        }
        uiQueue.push({
            id: id,
            ui: ui,
            deps: deps
        });
    };

    kity.extendClass(Minder, {
        /* 为实例注册 UI 交互 */
        initUI: function() {
            var ui = this._ui = {};
            var minder = this;

            uiQueue.forEach(function(uiDeal) {
                var deps = uiDeal.deps;
                if (deps) deps = deps.map(function(dep) {
                    return minder.getUI(dep);
                });
                ui[uiDeal.id] = uiDeal.ui.apply(null, [minder].concat(deps || []));
            });

            // 阻止非脑图事件冒泡
            $('#content-wrapper').delegate('#panel, #tab-container, .fui-dialog, #main-menu', 'keydown keyup', function(e) {
                e.stopPropagation();
            });

            // 阻止非脑图事件冒泡
            $('#content-wrapper').delegate('input', 'mousedown mousemove mouseup contextmenu', function(e) {
                e.stopPropagation();
            });

            minder.getPaper().addClass('loading-target');

            this.fire('interactchange');
            this.fire('uiready');
        },

        /* 获得实例的 UI 实例 */
        getUI: function(id) {
            return this._ui[id];
        }
    });

    $.ajaxSetup({ cache: false });
    $.extend($, {
        pajax: function() {
            var jqXHR = $.ajax.apply($, arguments);
            return new Promise(function(resolve, reject) {
                jqXHR.done(resolve);
                jqXHR.fail(function(jqXHR, textStatus, errorThrown) {
                    var e = new Error(textStatus);
                    e.getDetail = function() {
                        try {
                            return 'jQuery XHR Error: \n' + JSON.stringify(errorThrown);
                        } catch (e) {
                            return errorThrown;
                        }
                    };
                    reject(e);
                });
            });
        }
    });

    // preload css images
    $(function() {
        var list = ["kmcat_warn.png", "kmcat_sad.png", "icons.png", "template_large.png", "history.png", "feedback.png", "iconpriority.png", "iconprogress.png", "template.png", "layout.png", "next-level.png", "prev-level.png"];
        list.forEach(function(item) {
            (new Image()).src = 'ui/theme/default/images/' + item;
        });
    });

})();
/**
 * @fileOverview
 *
 * 简版事件解耦功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('eve', function(minder) {
    return {
        setup: function(obj) {
            var callbacks = {};
            
            obj.on = function on(name, callback) {
                var list = callbacks[name] || (callbacks[name] = []);
                list.push(callback);
                return this;
            };
            
            obj.off = function off(name, callback) {
                var list = callbacks[name];
                if (list) {
                    var index = list.indexOf(callback);
                    if (~index) {
                        list.splice(index, 1);
                    } else {
                        callback[name] = null;
                    }
                }
                return this;
            };
            
            obj.once = function once(name, callback) {
                return this.on(name, function wrapped() {
                    callback.apply(obj, arguments);
                    obj.off(name, wrapped);
                });
            };
            
            obj.fire = function fire(name) {
                var list = callbacks[name];
                var args = [].slice.call(arguments, 1);
                if (list) list.forEach(function(callback) {
                    callback.apply(obj, args);
                });
                return this;
            };

            return obj;
        }
    };
});
/**
 * @fileOverview
 *
 * UI 状态记忆
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('memory', function () {

    var ls = window.localStorage;
    var memory = ls.uiMemory ? JSON.parse(ls.uiMemory) : {};

    return {
        get: function(item) {
            return memory[item] || null;
        },

        set: function(item, value) {
            memory[item] = value;
            ls.uiMemory = JSON.stringify(memory);
        }
    };
});
/**
 * @fileOverview
 *
 * 拓展 FUI 组件的功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

kity.extendClass(FUI.Widget, {
    setEnable: function(value) {
        if (value === false) this.disable();
        else this.enable();
    },

    setActive: function(value) {
        if (value === false) this.removeClass('active');
        else this.addClass('active');
    },

    bindExecution: function(event, fn) {
        var widget = this;
        widget.on(event, function() {
            if (widget.interactFlag) return;
            fn.apply(widget, arguments);
        });
    },

    bindCommandState: function(minder, command, valueHandle) {
        var widget = this;
        minder.on('interactchange', function() {
            widget.interactFlag = true;
            if (valueHandle) {
                var value = this.queryCommandValue(command);
                if (value != widget.lastHandleCommandValue) {
                    valueHandle.call(widget, value);
                    widget.lastHandleCommandValue = value;
                }
            }
            widget.setEnable(this.queryCommandState(command) !== -1);
            widget.setActive(this.queryCommandState(command) === 1);
            widget.interactFlag = false;
        });
    }
});
/**
 * @fileOverview
 *
 * XSS Protection
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('axss', function() {
    function axss(value) {
        var div = document.createElement('div');
        div.innerHTML = value;
        $(div).find('script, iframe, link').remove();
        for (var name in div) {
            if (name.indexOf('on') === 0) {
                div.removeAttribute(name);
            }
        }
        return div.innerHTML;
    }
    return axss;
});
/**
 * @fileOverview
 *
 * 拓展 fio 的能力
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('fiox', function(minder) {
    var eve = minder.getUI('eve');
    eve.setup(fio.user);
    
    /* 初始化网盘使用的 APP 身份 */
    fio.user.init({
        apiKey: 'wiE55BGOG8BkGnpPs6UNtPbb'
    });
});
/**
 * @fileOverview
 *
 * 生成绑定到某个命令的按钮
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('widget/commandbutton', function(minder) {

    return {

        generate: function(command, onclick) {
            var $button = new FUI.Button({
                label: minder.getLang('ui.command.' + command) || minder.getLang('ui.' + command),
                text: minder.getLang('ui.command.' + command) || minder.getLang('ui.' + command),
                className: ['command-widget', 'command-button', command]
            });

            $button.bindExecution('click', onclick || function() {
                minder.execCommand(command);
            });

            $button.bindCommandState(minder, command);

            return $button;
        }
    };
});
/**
 * @fileOverview
 *
 * commandbuttonset.js 生成与指定命令绑定的按钮
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('widget/commandbuttonset', function(minder) {
    function mapValueItem(command, valueList) {
        return valueList.map(function(value) {
            var text = minder.getLang([command, value].join('.')) || value;
            return {
                label: text,
                text: text,
                value: value,
                className: [command, value].join(' ')
            };
        });
    }

    function generate(command, valueList) {

        var $buttonset = new FUI.Buttonset({
            id: 'template-set',
            buttons: typeof(valueList[0]) == 'object' ? valueList : mapValueItem(command, valueList),
            className: ['command-widget', 'command-buttonset', command].join(' ')
        });


        $buttonset.bindExecution('change', function() {
            minder.execCommand(command, $buttonset.getValue());
        });

        $buttonset.bindCommandState(minder, command, function(value) {
            this.selectByValue(value);
        });

        return $buttonset;
    }

    return {
        generate: generate
    };
});
/**
 * @fileOverview
 * 
 * 绑定到某个命令的下拉选框
 *
 */
KityMinder.registerUI('widget/commandinputmenu', function(minder) {

    function generate(command, menuList) {

        var $menu = new FUI.InputMenu({
            menu: {
                items: menuList
            },
            input: {
                placeholder: minder.getLang('ui.' + command),
            },
            className: ['command-widget', 'command-inputmenu', command]
        });


        $menu.bindCommandState(minder, command, function(value) {
            if (!$menu.selectByValue(value)) {
                $menu.clearSelect();
            }
        });

        var lastIndex = -1;
        $menu.bindExecution('select', function(e, info) {
            if (~info.index) {
                minder.execCommand(command, info.value);
            } else {
                $menu.select(lastIndex);
            }
            lastIndex = info.index;
        });

        return $menu;
    }

    return {
        generate: generate
    };
});
/**
 * @fileOverview
 *
 * 生成与指定命令绑定的下拉选框
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('widget/commandselectmenu', function(minder) {
    function mapValueWidget(command, valueList) {
        return valueList.map(function(value) {
            var text = minder.getLang([command, value].join('.')) || value;
            return {
                clazz: 'Button',
                label: text,
                text: text,
                value: value,
                className: [command, value].join(' ')
            };
        });
    }

    function generate(command, valueList, column) {

        var $selectMenu = new FUI.SelectMenu({
            widgets: typeof(valueList[0]) == 'object' ? valueList : mapValueWidget(command, valueList),
            className: ['command-widget', 'command-selectmenu', command].join(' '),
            column: column || 3
        });

        $selectMenu.bindExecution('change', function() {
            minder.execCommand(command, $selectMenu.getValue());
        });

        $selectMenu.bindCommandState(minder, command, function(value) {
            if (value !== undefined) this.selectByValue(value);
        });

        return $selectMenu;
    }

    return {
        generate: generate
    };
});
/**
 * @fileOverview
 *
 * 通知小组件
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('widget/notice', function(minder) {
    var errorMessage = minder.getLang('error_message');
    var memory = minder.getUI('memory');

    var $notice = $('<div>')
        .addClass('notice-widget')
        .appendTo('#content-wrapper');
    var $mask = $('<div>')
        .addClass('error-mask');

    var $error = new FUI.Dialog({
        width: 500,
        height: 'auto',
        prompt: true,
        caption: errorMessage.title,
        className: 'error-dialog'
    }).appendTo(document.getElementById('content-wrapper'));

    $error.on('ok cancel', function(e) {
        if (error.resolve) error.resolve(e);
    });

    var $error_body = $($error.getBodyElement());

    var isBuilded = (function() {
        var scripts = [].slice.apply(document.getElementsByTagName('script'));
        var s, m;
        while ((s = scripts.pop())) {
            if ((m = /kityminder.*\.min\.js/.exec(s.src))) return m[0];
        }
        return false;

    })();

    // concatMap: sperate files -> join file
    // minMap: join file -> min file
    var concatMap, minMap;

    function fixSourceSymbol($ta, $mask) {

        function fix() {
            var text = $ta.text();
            var pattern = new RegExp('at.+' + isBuilded + '.+\\:(\\d+)\\:(\\d+)\\)?', 'g');
            var match;

            $ta.text(text.replace(pattern, function(match, $1, $2) {
                var lookup = {
                    line: +$1,
                    column: +$2
                };
                var info = minMap.originalPositionFor(lookup);
                var name = info.name;
                lookup = {
                    line: info.line,
                    column: info.column
                };
                info = concatMap.originalPositionFor(lookup);
                name = name || '<Anonymous>';
                var replaced = 'at ' + name + ' (' +
                    info.source.replace('../', '') + ':' + info.line + ':' + info.column + ')';
                if (replaced.indexOf('promise') != -1) {
                    replaced = 'at <async> Promise.' + name;
                }
                return replaced;
            }));
        }

        if (isBuilded) {

            if (concatMap) return fix();

            $mask.addClass('loading');

            setTimeout(function() {
                $mask.removeClass('loading');
            }, 5000);

            var script = document.createElement('script');
            script.onload = function() {
                Promise.all([

                    $.pajax({
                        url: isBuilded.replace('min.js', 'js.map'),
                        dataType: 'json'
                    }),

                    $.pajax({
                        url: isBuilded.replace('.js', '.map'),
                        dataType: 'json'
                    })

                ]).then(function(files) {
                    concatMap = new window.sourceMap.SourceMapConsumer(files[0]);
                    minMap = new window.sourceMap.SourceMapConsumer(files[1]);
                    fix();
                    $mask.removeClass('loading');
                });
            };
            script.src = 'lib/source-map.min.js';
            document.head.appendChild(script);
        }
    }

    $error_body.delegate('.error-detail a.expander', 'click', function(e) {
        var $detail = $(e.target).closest('.error-detail').toggleClass('expanded');
        var showDetail = $detail.hasClass('expanded');

        memory.set('show-error-detail', showDetail);
    });

    function info(msg, warn, time) {

        if (!$notice.hasClass('show')) $notice.empty();

        clearTimeout(info.ttl2);

        if (warn) $notice.addClass('warn');
        else $notice.removeClass('warn');

        var $menu = minder.getUI('menu/menu');
        $notice.css({
            top: $menu && $menu.isVisible() ?
                $('#main-menu .main-menu-level1').offset().top :
                $('#kityminder').offset().top + 20
        });
        $notice.append($('<p>').text(msg));
        $notice.addClass('show');

        clearTimeout(info.ttl);

        time = time || (warn ? 5000 : 3000);
        info.ttl = setTimeout(function() {
            $notice.removeClass('show');
            info.ttl2 = setTimeout(function() {
                $notice.empty();
            }, 1000);
        }, time);
    }

    function warn(msg) {
        info(msg, warn);
    }

    function descriptReason(e) {
        e = e || new Error();

        if (typeof(e) == 'string') {
            e = new Error(e);
        }

        if (e.getDetail) return e;

        // 文件访问错误
        if (typeof(fio) != 'undefined' && (e instanceof fio.FileRequestError)) {
            if (!e.status) {
                e.description = errorMessage.err_network;
            } else {
                e.description = errorMessage.pcs_code[e.detail.error_code];
            }
            e.getDetail = function() {
                return JSON.stringify(e, null, 4);
            };
        }
        
        // jqXhr
        else if ('readyState' in e) {
            
        } else {
            e.getDetail = function() {
                return e.stack || new Error().stack;
            };
        }

        return e;
    }

    function error(name, e) {
        if (arguments.length == 1) {
            e = name;
            name = 'unknown';
        }
        $error_body.empty();

        e = descriptReason(e);

        var $content = $('<div>')
            .addClass('error-content')
            .appendTo($error_body);

        var $msg = $('<h3>')
            .text(errorMessage[name] || errorMessage.err_unknown)
            .appendTo($content);
        var $reason = $('<p>')
            .text(e.message || e.description || errorMessage.unknownreason)
            .appendTo($content);

        if (e.getDetail) {
            var $detail = $('<div>')
                .addClass('error-detail')
                .append($('<a class="expander"></a>').text(minder.getLang('ui.error_detail')))
                .appendTo($error_body);

            var $detailContent = $('<div>')
                .addClass('error-detail-wrapper')
                .appendTo($detail);

            var $textarea = $('<textarea>')
                .attr('id', 'error-detail-content')
                .text(e.getDetail() + '\n\n浏览器信息：' + navigator.userAgent)
                .appendTo($detailContent);

            fixSourceSymbol($textarea, $detailContent);

            var $copy = $('<button>')
                .addClass('copy-and-feedback')
                .text(minder.getLang('ui.copy_and_feedback'))
                .appendTo($detailContent);

            $copy.attr('data-clipboard-target', 'error-detail-content');

            zeroCopy($copy);

            if (memory.get('show-error-detail')) $detail.addClass('expanded');
        }

        $error.show();
        $error.getElement().style.top = '180px';

        return new Promise(function(resolve) {
            error.resolve = resolve;
        });
    }


    function zeroCopy($target) {

        /* global ZeroClipboard:true */

        if (window.ZeroClipboard) {
            ZeroClipboard.config({
                swfPath: 'lib/ZeroClipboard.swf',
                hoverClass: 'hover',
                activeClass: 'active'
            });
            var clip = new window.ZeroClipboard($target);
            clip.on('ready', function() {
                clip.on('aftercopy', function() {
                    $error.hide();
                    minder.getUI('topbar/feedback').click();
                });
            });
        } else {
            $target.remove();
        }
    }

    return {
        info: info,
        error: error,
        warn: warn
    };

});
/**
 * @fileOverview
 *
 * 渲染当前时间离指定时间的时长
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('widget/friendlytimespan', function(minder) {

    $.extend($.fn, {
        displayFriendlyTime: function(time) {
            return this.each(function() {
                display($(this)
                    .addClass('friendly-time')
                    .data('time', time));
            });
        }
    });

    function getTimeText(timeInMs) {
        var ms = Math.abs(timeInMs - new Date()),
            s = ms / 1000,
            m = s / 60,
            h = m / 60,
            d = h / 24;
        if (s < 60) return minder.getLang('ui.justnow', s | 0);
        if (m < 60) return minder.getLang('ui.minutesago', m | 0);
        if (h < 24) return minder.getLang('ui.hoursago', h | 0);
        if (d < 2) return minder.getLang('ui.yesterday');
        if (d <= 30) return minder.getLang('ui.daysago', d | 0);

        return minder.getLang("ui.longago");
    }

    function display($element) {
        $element.text(getTimeText($element.data('time')));
    }

    function update() {
        $('.friendly-time').each(function() {
            display($(this));
        });
    }

    setInterval(update, 60000);
});
/**
 * @fileOverview
 *
 * 提供存储在 LocalStorage 中的列表
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('widget/locallist', function() {
    function LocalList(name, maxCount) {
        var list;

        maxCount = maxCount || 10;

        function load() {
            list = localStorage.getItem(name);
            if (list) {
                list = JSON.parse(list);
            } else {
                list = [];
            }
            this.length = list.length;
        }

        function save() {
            while (list.length > maxCount) list.pop();
            localStorage.setItem(name, JSON.stringify(list));
            this.length = list.length;
        }

        function get(index) {
            return list[index];
        }

        function remove(index) {
            list.splice(index, 1);
            save();
        }

        function clear() {
            list = [];
            save();
        }

        function unshift(item) {
            list.unshift(item);
            save();
        }

        function createKeyMatcher(key) {
            return function(item, value) {
                return item[key] == value;
            };
        }

        function findIndex(matcher, value) {
            if (typeof(matcher) == 'string') {
                matcher = createKeyMatcher(matcher);
            }
            for (var i = 0; i < list.length; i++) {
                if (matcher(list[i], value)) return i;
            }
            return -1;
        }

        function find(matcher, value) {
            return get(findIndex(matcher, value));
        }

        function forEach(callback) {
            list.forEach(callback);
            save();
        }

        load.call(this);

        this.get = get;
        this.remove = remove;
        this.findIndex = findIndex;
        this.find = find;
        this.forEach = forEach;
        this.unshift = unshift;
        this.clear = clear;
    }

    return {
        use: function(name) {
            return new LocalList(name);
        }
    };
});
/**
 * @fileOverview
 *
 * 网盘的目录访问组件
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('widget/netdiskfinder', function(minder) {

    var eve = minder.getUI('eve');
    var notice = minder.getUI('widget/notice');
    var recycleReady = null;
    var base = '/apps/kityminder';
    var recyclePath = base + '/.recycle';
    var moveConfirm = true;
    var instances = [];
    var Finder = eve.setup({});

    Finder.BASE_PATH = base + '/';
    Finder.RECYCLE_PATH = recyclePath + '/';

    Finder.on('mv', function(from, to, source) {
        instances.forEach(function(instance) {
            if (source != instance) instance.refresh();
        });
    });

    /**
     * 生成一个网盘的目录访问组件
     *
     * @param  {JQueryObject} $container 容器
     * @param  {function} listFilter  一个函数，检查一个文件是否应该被列出
     */
    function generate($container, listFilter) {

        var finder = eve.setup({});

        instances.push(finder);

        var currentPath = base;

        var $finder = $('<div class="netdisk-finder"></div>').appendTo($container);

        /* 顶部工具栏 */
        var $headbar = $('<div class="head"></div>').appendTo($finder);

        /* 控制按钮 */
        var $control = $('<div class="control"></div>').appendTo($headbar);

        var $mkdir = $('<a></a>')
            .text(minder.getLang('ui.mkdir'))
            .attr('title', minder.getLang('ui.mkdir'))
            .addClass('button mkdir')
            .appendTo($control)
            .click(mkdir);

        var $recycle = $('<a></a>')
            .text(minder.getLang('ui.recycle'))
            .attr('title', minder.getLang('ui.recycle'))
            .addClass('button recycle dir')
            .data('file', {
                path: recyclePath,
                filename: minder.getLang('ui.recycle')
            })
            .appendTo($control)
            .click(recycle);

        var $recycleClear = $('<a></a>')
            .text(minder.getLang('ui.recycle_clear'))
            .attr('title', minder.getLang('ui.recycle_clear'))
            .addClass('button recycle-clear')
            .appendTo($control)
            .click(clearRecycle);


        /* 路径导航 */
        var $nav = $('<div class="nav"></div>').appendTo($headbar);

        /* 显示当前目录文件列表 */
        var $list = $('<ul class="file-list"></ul>')
            .appendTo($finder);

        var selected = null;

        minder.on('uiready', function() {
            var $user = minder.getUI('topbar/user');
            $user.requireLogin($container);
            fio.user.on('login', function() {
                list();
            });
        });

        handleClick();
        handleDrag();
        handleNav();
        handleRename();

        function handleRename() {

            $list.delegate('.file-list-item a.rename-button', 'click', function(e) {
                var $li = $(e.target).closest('li');
                $li.find('span.filename').remove();
                rename($li);
                $li.addClass('renaming');
                e.stopPropagation();
            });

            function rename($li) {
                rename.onprogress = true;
                var file = $li.data('file');

                var $input = $('<input>')
                    .attr('type', 'text')
                    .addClass('new-dir-name fui-widget fui-selectable')
                    .val(file.filename)
                    .appendTo($li);

                $li.removeAttr('draggable');

                $input.on('keydown', function (e) {
                    if (e.keyCode == 13) return confirm();
                    if (e.keyCode == 27) {
                        e.stopPropagation();
                        return cancel();
                    }
                }).on('blur', cancel);

                $input.on('dragstart mousedown mouseup click dblclick', function(e) {
                    e.stopPropagation();
                });

                setTimeout(function() {
                    $input[0].select();
                });

                function reset(filename) {
                    $input.remove();
                    $li.find('.icon').after('<span class="filename">' + filename + '</span>');
                    $li.removeClass('renaming');
                    $li.attr('draggable', true);
                }

                function cancel() {
                    reset(file.filename);
                }

                function confirm() {
                    var newFilename = $input.val();
                    var newPath = file.parentPath + newFilename;

                    if (file.filename == newFilename) return cancel();
                    if (fio.file.anlysisPath(newFilename).extension != file.extension) {
                        $input.addClass('invalid-name');
                        setTimeout(function () {
                            $input.removeClass('invalid-name'); 
                        }, 500);
                        return $input.select();
                    }

                    $container.addClass('loading');

                    mv(file.path, newPath).then(function () {

                        var oldPath = file.path;
                        file.filename = newFilename;
                        file.path = newPath;
                        reset(newFilename);
                        Finder.fire('mv', oldPath, newPath, finder);
                        notice.info(minder.getLang('ui.rename_success', newFilename));

                    })['catch'](function(e) {

                        notice.error('err_rename', e);
                        cancel();

                    }).then(function() {

                        $container.removeClass('loading');
                        
                    });
                }
            }
        }

        function handleClick() {
            /* 点击目录中的项目时打开项目 */
            $list.delegate('.file-list-item', 'dblclick', function(e) {
                if (currentPath == recyclePath + '/') return;
                if (mkdir.onprogress) return mkdir.onprogress.select();
                var $file = $(e.target).closest('li'),
                    file = $file.data('file');
                if (file) open(file);
            });
            $list.delegate('.file-list-item', 'mousedown', function(e) {
                if (mkdir.onprogress) return mkdir.onprogress.select();
                var $file = $(e.target).closest('li'),
                    file = $file.data('file');
                if (!file) return;
                select(file && file.path);
            });
        }

        function handleNav() {
            /* 点击导航处，切换路径 */
            $nav.delegate('a', 'click', function(e) {
                if (mkdir.onprogress) return mkdir.onprogress.select();
                if ($(e.target).hasClass('dir-back')) {
                    var parts = currentPath.split('/');
                    parts.pop(); // 有一个无效部分
                    parts.pop();
                    return list(parts.join('/'));
                }
                list($(e.target).data('path'));
            });
        }

        function handleDrag() {

            var fileItemSelector = '.file-list-item';
            var dirSelector = '.dir';
            var $dragging = null;

            $list.delegate(fileItemSelector, 'dragstart', itemDragStart)
                .delegate(fileItemSelector, 'dragend', itemDragEnd)
                .delegate(dirSelector, 'dragover', dragOver)
                .delegate(dirSelector, 'dragenter', dirDragEnter)
                .delegate(dirSelector, 'dragleave', dirDragLeave)
                .delegate(dirSelector, 'drop', dirDrop);

            $headbar.delegate(dirSelector, 'dragover', dragOver)
                .delegate(dirSelector, 'dragenter', dirDragEnter)
                .delegate(dirSelector, 'dragleave', dirDragLeave)
                .delegate(dirSelector, 'drop', dirDrop);

            $list.delegate(fileItemSelector + ' input', 'dragstart', function(e) {
                e.stopPropagation();
                e.preventDefault();
            });

            function itemDragStart(e) {
                var $target = $(e.target);
                if (!$target.hasClass('file-list-item')) {
                    return;
                }
                // e.originalEvent.dataTransfer.effectAllowed = "move";
                // e.originalEvent.dataTransfer.dropEffect = 'move';

                try {
                    var dataType = kity.Browser.ie && kity.Browser.version == 10 ? 'text' : 'text/plain';
                    e.originalEvent.dataTransfer.setData(dataType, 'FEX');
                    e.originalEvent.dataTransfer.setDragImage($target.find('.icon').get(0), 12, 12);
                } catch (ignore) {}

                $dragging = $target.addClass('dragging');
                $finder.addClass('drop-mode');
            }

            function itemDragEnd(e) {
                $(e.target).removeClass('dragging');
                e.originalEvent.dataTransfer.dropEffect = 'move';
                e.preventDefault();
                $finder.removeClass('drop-mode');
            }

            function dragOver(e) {
                if ($(e.target).hasClass('filename')) e.preventDefault();
            }

            function dirDragEnter(e) {
                var $target = $(e.target).closest('.dir');
                $target.addClass('drag-enter');
                if (e.target != $target[0]) $target.addClass('enter-child');
            }

            function dirDragLeave(e) {
                if ($(e.target).hasClass('dir')) {
                    if ($(e.target).hasClass('enter-child')) {
                        return $(e.target).removeClass('enter-child');
                    }
                    $(e.target).removeClass('drag-enter');
                }
            }

            function dirDrop(e) {
                e.preventDefault();
                
                var $target = $(e.target).closest('.dir').removeClass('drag-enter');

                if (!$target.hasClass('dir')) return;

                var source = $dragging.data('file');
                var destination = $target.data('file');

                var destinationPath = destination.path + '/' + source.filename;
                var sourcePath = source.path;

                if (destinationPath.indexOf(sourcePath) === 0) return;


                if (!moveConfirm || window.confirm(minder.getLang('ui.move_file_confirm', source.filename, destination.filename))) {
                    $container.addClass('loading');
                    recycleReady.then(doMove);
                    moveConfirm = false;
                }

                function doMove() {
                    mv(sourcePath, destinationPath).then(function() {
                        $dragging.remove();
                        Finder.fire('mv', sourcePath, destinationPath, finder);
                        notice.info(minder.getLang('ui.move_success', source.filename, destination.filename));
                    })['catch'](function(e) {
                        notice.error('err_move_file', e);
                    }).then(function() {
                        $container.removeClass('loading');
                    });
                }
            }
        }

        function recycle() {
            list(recyclePath);
        }

        function createRecycleBin() {
            return fio.file.mkdir({
                path: recyclePath
            });
        }

        function clearRecycle() {
            if (!window.confirm(minder.getLang('ui.recycle_clear_confirm'))) return;

            $container.addClass('loading');

            fio.file['delete']({
                path: recyclePath
            }).then(function() {
                return recycleReady = createRecycleBin();
            }).then(function() {
                renderList([]);
                $container.removeClass('loading');
            });
        }

        function mv(source, destination) {
            return fio.file.move({
                path: source,
                newPath: destination,
                ondup: destination.indexOf(recyclePath) === 0 ? fio.file.DUP_RENAME : fio.file.DUP_FAIL
            });
        }

        function mkdir() {
            if (mkdir.onprogress) {
                return mkdir.onprogress.select();
            }

            var $li = $('<li>').addClass('file-list-item dir').prependTo($list);

            $li.append('<span class="icon"></span>');

            var $input = $('<input>')
                .attr('type', 'text')
                .addClass('new-dir-name fui-widget fui-selectable')
                .val(minder.getLang('ui.newdir'))
                .appendTo($li);

            mkdir.onprogress = $input[0];
            $input[0].select();

            $input.on('keydown', function(e) {
                if (e.keyCode == 13) confirm();
                if (e.keyCode == 27) {
                    cancel();
                    e.stopPropagation();
                }
            }).on('blur', confirm);

            function cancel() {
                $li.remove();
                mkdir.onprogress = false;
            }

            function confirm() {
                var name = $input.val();
                if (name) {
                    $container.addClass('loading');
                    fio.file.mkdir({
                        path: currentPath + name
                    }).then(function() {
                        return new Promise(function(resolve) {
                            setTimeout(function() {
                                resolve(refresh());
                            }, 200);
                        });
                    }, function(e) {
                        if (e.detail && e.detail.error_code == 31061) {
                            e.message = '已存在同名目录';
                        }
                        var notice = minder.getUI('widget/notice');
                        notice.error('err_mkdir', e);
                        $li.remove();
                    }).then(function() {
                        $container.removeClass('loading');
                        mkdir.onprogress = false;
                    });
                }
            }
        }

        /**
         * 返回数值的符号：
         *     正数 => 1
         *       0 => 0
         *     负数 => -1
         */
        function sign(num) {
            return num > 0 ? 1 : (num < 0 ? -1 : 0);
        }

        /**
         * 打开选中的文件或目录
         *
         * @param  {fio.file.File} file
         */
        function open(file) {
            if (file.isDir) return list(file.path);
            finder.fire('fileclick', file);
        }

        function fadeOutList(x) {
            return new Promise(function(resolve, reject) {
                $list.transit({
                    x: x,
                    opacity: 0
                }, 100, resolve);
            });
        }

        function fadeInList() {
            return new Promise(function(resolve) {
                $list.css({
                    x: -parseInt($list.css('x'))
                }).stop().transit({
                    x: 0,
                    opacity: 1
                }, 100, resolve);
            });
        }

        function refresh() {
            return list(currentPath, true);
        }

        /**
         * 列出指定目录的文件
         */
        function list(path, noAnimate) {

            path = path || base;

            var listPromise = fio.file.list({
                path: path
            });

            var transitPromise = noAnimate ? Promise.resolve() : fadeOutList(-100 * sign(path.length - currentPath.length));

            currentPath = path.charAt(path.length - 1) == '/' ? path : path + '/';

            updateNav();

            function checkRecycleBin(files) {
                if (!recycleReady && path == base) {
                    for (var i = 0; i < files.length; i++) {
                        if (files[i].path == recyclePath) {
                            recycleReady = Promise.resolve(true);
                        }
                        break;
                    }
                    recycleReady = recycleReady || createRecycleBin();
                }
            }

            return Promise.all([listPromise, transitPromise]).then(function(values) {

                var files = values[0];
                checkRecycleBin(files);
                return renderList(files);

            }, function(error) {

                var notice = minder.getUI('widget/notice');
                notice.error('err_ls', error);
            });
        }

        function renderFileList(files) {

            $list.empty();
            if (!files.length) {
                $list.append('<li class="empty" disabled="disabled">' + minder.getLang('ui.emptydir') + '</li>');
            } else {
                files.forEach(function(file) {
                    if (!file.isDir && (!listFilter || !listFilter(file))) return;
                    if (file.path == recyclePath) return;

                    $('<li></li>')
                        .append('<span class="icon"></span>')
                        .append('<span class="filename">' + file.filename + '</span>')
                        .append('<a class="rename-button" title="' + minder.getLang('ui.rename') + '">"' + minder.getLang('ui.rename') + '"</a>')
                        .addClass('file-list-item')
                        .addClass(file.isDir ? 'dir' : 'file')
                        .data('file', file)
                        .attr('draggable', true)
                        .appendTo($list);
                });
            }
        }

        finder._renderFileList = renderFileList;

        function renderList(files) {

            files.sort(function(a, b) {
                if (a.isDir > b.isDir) {
                    return -1;
                } else if (a.isDir == b.isDir) {
                    return a.createTime > b.createTime ? -1 : 1;
                } else return 1;
            });

            renderFileList(files);

            // 通知其他 finder 更新
            instances.forEach(function(instance) {
                if (instance == finder) return;
                if (instance.pwd() == currentPath)
                    instance._renderFileList(files);
            });

            fadeInList();
            checkSelect();

            finder.fire('cd', currentPath);
        }

        function updateNav() {
            $nav.empty();
            if (currentPath != base && currentPath != base + '/') {
                $nav.append('<a class="dir-back">Back</a>');
            } else {
                $nav.append('<span class="my-document"></span>');
            }
            var path = currentPath.substr(base.length);
            var parts = path.split('/');
            var processPath = '';

            function pathButton(part) {
                processPath += part + '/';
                var $a = $('<a></a>').addClass('dir');
                if (part == base) {
                    $a.text(minder.getLang('ui.mydocument'));
                } else if (part == '.recycle') {
                    $a.text(minder.getLang('ui.recycle'));
                    $finder.addClass('recycle-bin');
                } else {
                    $a.text(part);
                }
                return $a.data('path', processPath).data('file', {
                    path: processPath.substr(0, processPath.length - 1),
                    filename: part == base ? minder.getLang('ui.mydocument') : part
                });
            }

            $finder.removeClass('recycle-bin');

            $nav.append(pathButton(base));

            parts.forEach(function(part) {
                if (!part) return;
                $nav.append('<span class="spliter"></span>');
                $nav.append(pathButton(part));
            });
        }

        function select(path) {
            selected = path;
            return checkSelect();
        }

        function checkSelect() {
            var hasSelect = false;
            $list.find('.file-list-item').removeClass('selected').each(function() {
                var file = $(this).data('file');
                if (file && file.path == selected) {
                    $(this).addClass('selected');
                    hasSelect = true;
                    $list[0].focus();
                    finder.fire('select', file, this);
                }
            });
            if (!hasSelect) selected = false;
            return hasSelect;
        }

        function pwd() {
            return currentPath;
        }

        finder.list = list;
        finder.select = select;
        finder.pwd = pwd;
        finder.refresh = refresh;

        return finder;
    }

    Finder.generate = generate;
    return Finder;
});
/**
 * @fileOverview
 *
 * 用 FUI.Tabs 实现的多级的创建
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KM.registerUI('widget/menutab', function(minder) {

    function generate(parent, name, asDefault) {
        var index = parent.getButtons().length;
        var tab = parent.appendTab({
            buttons: [{
                label: minder.getLang('ui.menu.' + name + 'tab'),
                className: 'tab-' + name
            }]
        });
        if (asDefault) {
            parent.select(index);
        }
        return tab[0].panel.getContentElement();
    }

    return {
        generate: generate
    };
});
/**
 * @fileOverview
 *
 * 当前文档管理
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('doc', function(minder) {

    var ret = minder.getUI('eve').setup({});
    var current = { saved: true };
    var loading = false;
    var notice = minder.getUI('widget/notice');
    var finder = minder.getUI('widget/netdiskfinder');

    if (finder) finder.on('mv', trackFileMove);

    function trackFileMove(from, to) {

        if (current.source != 'netdisk') return;

        var fromPath = from.split('/');
        var toPath = to.split('/');

        function preCommonLength(a, b) {
            var i = 0;
            while ((i in a) && (i in b) && a[i] == b[i]) i++;
            return (i in b) ? 0 : i;
        }


        var originPath = current.path.split('/');
        var clen = preCommonLength(originPath, fromPath);
        if (clen) {
            var movedPath = toPath.concat(originPath.slice(clen));
            current.path = movedPath.join('/');
            current.title = movedPath.pop();
            ret.fire('docchange', current);
        }
    }

    var locked = false;

    ret.lock = function() { locked = true; };
    ret.unlock = function() { locked = false; };

    /**
     * 加载文档
     *
     * @param  {Object} doc 文档的属性，可包括：
     *     doc.content  {string} [Required] 文档内容
     *     doc.protocol {string} [Required] 内容所使用的编码协议
     *     doc.title    {string} 文档的标题
     *     doc.source   {string} 文档的来源
     *     doc.path     {string} 文档的路径
     *     doc.saved    {bool}   文档的保存状态
     *
     * @event docload(doc)
     *     doc - 文档解析之后的文档对象
     *
     * @return {Promise<doc>} 返回解析完之后的文档对象，解析的结果为 doc.data
     */
    function load(doc) {
        if (locked) return Promise.reject(new Error('doc was locked'));

        var restore = doc;

        current = doc;

        loading = true;

        return minder.importData(doc.content, doc.protocol).then(function(data) {

            doc.title = doc.title || minder.getMinderTitle();

            minder.execCommand('camera', minder.getRoot(), 300);

            doc.data = data;
            doc.json = JSON.stringify(data);

            ret.fire('docload', doc);
            ret.fire('docchange', doc);

            return doc;

        })['catch'](function(e) {
            current = restore;
            notice.error('err_doc_resolve', e);
        }).then(function(doc) {
            loading = false;
            if (doc)
                notice.info(minder.getLang('ui.load_success', doc.title));
            return doc;
        });
    }

    function save(doc) {
        current = doc;
        doc.data = minder.exportJson();
        doc.json = JSON.stringify(doc.data);
        doc.saved = true;

        ret.fire('docsave', doc);
        ret.fire('docchange', doc);
    }

    function getCurrent() {
        return current;
    }

    function checkSaved(noConfirm) {
        if (!fio.user.current()) return true;
        if (locked) return false;
        if (noConfirm) return current.saved;
        return current.saved || window.confirm(minder.getLang('ui.unsavedcontent', '* ' + current.title));
    }

    /* 绕开初始化时候的乱事件 */
    setTimeout(function() {
        minder.on('contentchange', function() {
            if (loading) return;

            if (current.source != 'netdisk') {

                current.title = minder.getMinderTitle();
                current.saved = false;

            } else {
                current.saved = current.json == JSON.stringify(minder.exportJson());
            }

            ret.fire('docchange', current);
        });
    }, 1000);


    ret.load = load;
    ret.save = save;
    ret.current = getCurrent;
    ret.checkSaved = checkSaved;

    return ret;
});
/**
 * @fileOverview
 *
 *
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('contextmenu', function(minder) {
    var mac = kity.Browser.mac;

    function camel(word) {
        return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
    }

    var $menu = $('<ul>')
        .addClass('km-context-menu fui-popup-menu')
        .appendTo('#content-wrapper');

    var downPosition;

    function distance(p1, p2) {
        var dx = p1[0] - p2[0];
        var dy = p1[1] - p2[1];
        var ds = Math.sqrt(dx * dx + dy * dy);
        return ds;
    }

    $menu.delegate('li', 'mousedown', function(e, info) {
        var item = $(e.target).closest('li').data('menu');
        if (item.fn) {
            return item.fn.call(minder, minder);
        }
        if (item.command) {
            return minder.execCommand(item.command);
        }
    });

    $('#content-wrapper').on('contextmenu', function(e) {
        e.preventDefault();
    });

    $('#content-wrapper').on('mousedown', function(e) {
        $menu.hide();
        if (e.button == 2) {
            downPosition = [e.pageX, e.pageY];
        } else {
            downPosition = null;
        }
    });

    minder.on('mouseup', function(e) {
        //e.preventDefault();

        if (!e.isRightMB()) return;

        e = e.originEvent;
        
        var d = distance(downPosition, [e.pageX, e.pageY]);
        if (isNaN(d) || d > 5) return;

        $menu.empty();

        var ctxmenu = minder.getContextMenu();

        var lastDivider = true;
        ctxmenu.forEach(function(item) {
            var query = item.query || function() {
                return item.command && minder.queryCommandState(item.command) === 0;
            };
            if (query()) {

                var label = minder.getLang('ui.command.' + item.command);

                var $li = $('<li>')
                    .addClass('fui-item')
                    .data('menu', item)
                    .appendTo($menu);

                var shortcuts = minder.getCommandShortcutKey(item.command);
                if (shortcuts) {
                    shortcuts.split('|').forEach(function(shortcut) {
                        var $shortcut = $('<span>').addClass('shortcut').appendTo($li);
                        shortcut.split('+').forEach(function(key) {
                            var parts = key.split('::');
                            key = parts.length > 1 ? parts[1] : parts[0];
                            $('<span>').addClass('shortcut-key ' + key.toLowerCase())
                                .text(camel(key))
                                .appendTo($shortcut);
                        });
                        if (mac) $shortcut.addClass('mac');
                    });
                }

                $li.append($('<div>').text(label).addClass('menu-label'));

                lastDivider = false;
            }

            if (item.divider && !lastDivider) {
                $('<li>').addClass('divider').appendTo($menu);
                lastDivider = true;
            }
        });

        if (ctxmenu.length) {

            $menu.show();

            var x = e.pageX,
                y = e.pageY,
                width = $menu.outerWidth(),
                height = $menu.outerHeight(),
                clientWidth = document.body.clientWidth,
                clientHeight = document.body.clientHeight;

            if (x + width > clientWidth) x -= width;
            if (y + height > clientHeight) y -= height;

            $menu.offset({
                left: x,
                top: y
            });
        }
    });
});
/**
 * @fileOverview
 *
 * 脑图缩略图导航功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('nav', function(minder) {

    var memory = minder.getUI('memory');

    var $navBar = $('<div>').addClass('nav-bar').appendTo('#content-wrapper');
    var $commandbutton = minder.getUI('widget/commandbutton');

    var $zoomIn = $commandbutton.generate('zoom-in').appendTo($navBar[0]);
    var $zoomPan = createZoomPan($navBar);
    var $zoomOut = $commandbutton.generate('zoom-out').appendTo($navBar[0]);

    var $previewNavigator = createViewNavigator();

    var $hand = $commandbutton.generate('hand').appendTo($navBar[0]);
    var $root = $commandbutton.generate('camera', function() {
        minder.execCommand('camera', minder.getRoot(), 600);
    }).appendTo($navBar[0]);

    var $previewTrigger = createPreviewTrigger($previewNavigator).appendTo($navBar);

    function createZoomPan($parent) {
        var $pan = $('<div>').addClass('zoom-pan').appendTo($parent);
        var zoomStack = minder.getOptions('zoom');
        var minValue = zoomStack[0];
        var maxValue = zoomStack[zoomStack.length - 1];
        var valueRange = maxValue - minValue;

        var totalHeight = $pan.height();
        function getHeight(value) {
            return (1 - (value - minValue) / valueRange) * totalHeight;
        }

        var $origin = $('<div>')
            .addClass('origin')
            .appendTo($pan)
            .css('y', getHeight(100));

        var $indicator = $('<div>')
            .addClass('indicator')
            .appendTo($pan)
            .css('y', getHeight(100));

        function indicate(value) {
            $indicator.animate({
                'y': getHeight(value)
            }, 200);
        }

        minder.on('zoom', function(e) {
            indicate(e.zoom);
        });

        $origin.click(function() {
            minder.execCommand('zoom', 100);
        });

        return $pan;
    }

    /**
     * 创建导航器的 DOM 元素以及交互的逻辑代码
     */
    function createViewNavigator() {
        var $previewNavigator = $('<div>')
            .addClass('preview-navigator')
            .appendTo('#content-wrapper');

        // 画布，渲染缩略图
        var paper = new kity.Paper($previewNavigator[0]);

        // 用两个路径来挥之节点和连线的缩略图
        var nodeThumb = paper.put(new kity.Path());
        var connectionThumb = paper.put(new kity.Path());

        // 表示可视区域的矩形
        var visibleRect = paper.put(new kity.Rect(100, 100).stroke('red', '1%'));

        var contentView = new kity.Box(), visibleView = new kity.Box();

        navigate();

        $previewNavigator.show = function() {
            $.fn.show.call(this);
            bind();
            updateContentView();
            updateVisibleView();
        };

        $previewNavigator.hide = function() {
            $.fn.hide.call(this);
            unbind();
        };

        function bind() {
            minder.on('layout layoutallfinish', updateContentView);
            minder.on('viewchange', updateVisibleView);
        }

        function unbind() {
            minder.off('layout layoutallfinish', updateContentView);
            minder.off('viewchange', updateVisibleView);
        }

        window.u = updateContentView;

        function navigate() {

            function moveView(center, duration) {
                var box = visibleView;
                center.x = -center.x;
                center.y = -center.y;

                var viewMatrix = minder.getPaper().getViewPortMatrix();
                box = viewMatrix.transformBox(box);

                var targetPosition = center.offset(box.width / 2, box.height / 2);

                minder.getViewDragger().moveTo(targetPosition, duration);
            }

            var dragging = false;

            paper.on('mousedown', function(e) {
                dragging = true;
                moveView(e.getPosition('top'), 200);
                $previewNavigator.addClass('grab');
            });

            paper.on('mousemove', function(e) {
                if (dragging) {
                    moveView(e.getPosition('top'));
                }
            });

            $(window).on('mouseup', function() {
                dragging = false;
                $previewNavigator.removeClass('grab');
            });
        }

        function updateContentView() {

            var view = minder.getRenderContainer().getBoundaryBox();

            contentView = view;

            var padding = 30;

            paper.setViewBox(
                view.x - padding - 0.5,
                view.y - padding - 0.5,
                view.width + padding * 2 + 1,
                view.height + padding * 2 + 1);

            var nodePathData = [];
            var connectionThumbData = [];

            minder.getRoot().traverse(function(node) {
                var box = node.getLayoutBox();
                nodePathData.push('M', box.x, box.y,
                    'h', box.width, 'v', box.height,
                    'h', -box.width, 'z');
                if (node.getConnection() && node.parent && node.parent.isExpanded()) {
                    connectionThumbData.push(node.getConnection().getPathData());
                }
            });

            paper.setStyle('background', minder.getStyle('background'));

            if (nodePathData.length) {
                nodeThumb
                    .fill(minder.getStyle('root-background'))
                    .setPathData(nodePathData);
            } else {
                nodeThumb.setPathData(null);
            }

            if (connectionThumbData.length) {
                connectionThumb
                    .stroke(minder.getStyle('connect-color'), '0.5%')
                    .setPathData(connectionThumbData);
            } else {
                connectionThumb.setPathData(null);
            }

            updateVisibleView();
        }

        function updateVisibleView() {
            visibleView = minder.getViewDragger().getView();
            visibleRect.setBox(visibleView.intersect(contentView));
        }

        return $previewNavigator;
    }

    function createPreviewTrigger($previewNavigator) {
        var $trigger = $('<div>').addClass('command-button nav-trigger');

        $trigger.append('<div class="fui-icon">');
        $trigger.click(toggle);
        $trigger.attr('title', minder.getLang('ui.navigator'));

        function toggle() {
            if ($trigger.toggleClass('active').hasClass('active')) {
                $previewNavigator.show();
                memory.set('navigator-hidden', false);
            } else {
                $previewNavigator.hide();
                memory.set('navigator-hidden', true);
            }
        }

        if (memory.get('navigator-hidden')) toggle();
        toggle();

        return $trigger;
    }

});

/**
 * @fileOverview
 *
 * 搜索节点功能
 *
 * @author: yangxiaohu
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('topbar/moreservice', function(minder) {
    var $service;
    var $link = $('<link>').attr('href', "http://baiduoffice.duapp.com/public/assets/style/moreService.css").attr('rel', "stylesheet");

    $('head').append($link);

    $.getScript('http://baiduoffice.duapp.com/public/widget/moreService.js', startService);

    $service =  $('<div id="moreservice"></div>');

    function startService(){
        $service.prependTo('#panel').moreService({button: {float: 'left', width: '40px', height: '40px', 'border-right': '1px solid rgba(255, 255, 255, .5)'}});
    };

    return $service;
});
/**
 * @fileOverview
 *
 * 主菜单控制
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/menu', function(minder) {

    var ret = minder.getUI('eve').setup({});
    var $menutab = minder.getUI('widget/menutab');

    // 主菜单容器
    var $panel = $('<div>')
        .attr('id', 'main-menu')
        .css('display', 'none')
        .appendTo('#content-wrapper');

    // 主菜单按钮
    var $button = new FUI.Button({

        id: 'main-menu-btn',
        label: minder.getLang('ui.menu.mainmenutext')

    }).appendTo(document.getElementById('panel'));

    // 一级菜单选项卡
    var $tabs = new FUI.Tabs({
        className: 'main-menu-level1'
    }).appendTo($panel[0]);

    var timer;

    function show() {
        $panel.css('display', 'block');
        clearTimeout(timer);
        timer = setTimeout(function() {
            $panel.addClass('show');
            ret.fire('show');
        });
    }

    function hide() {
        ret.fire('hide');
        $panel.removeClass('show');
        minder.getRenderTarget().focus();
        timer = setTimeout(function() {
            $panel.css('display', 'none');
        });
    }

    function isVisible() {
        return $panel.hasClass('show');
    }

    function toggle() {
        if ($('#content-wrapper').hasClass('fullscreen')) return;
        (isVisible() ? hide : show)();
    }

    function createSub(name, asDefault) {
        var $sub = $menutab.generate($tabs, name, asDefault);
        var $h2 = $('<h2></h2>')
            .text(minder.getLang('ui.menu.' + name + 'header'))
            .appendTo($sub);
        return $sub;
    }

    function createSubMenu(name, asDefault) {
        var $sub = createSub(name, asDefault);
        var $subtabs = new FUI.Tabs().appendTo($sub);
        return {
            $tabs: $subtabs,
            createSub: function(subname, asDefault) {
                return $menutab.generate($subtabs, subname, asDefault);
            }
        };
    }

    $button.on('click', toggle);

    minder.addShortcut('esc', toggle);

    // expose
    ret.show = show;
    ret.hide = hide;
    ret.toggle = toggle;
    ret.isVisible = isVisible;
    ret.createSub = createSub;
    ret.createSubMenu = createSubMenu;
    ret.$panel = $panel;
    ret.$button = $button;
    ret.$tabs = $tabs;

    return ret;
});
/**
 * @fileOverview
 *
 * 菜单头部
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/header', function(minder) {

    var $menu = minder.getUI('menu/menu');

    var $header = $('<div class="main-menu-header"></div>')
        .prependTo($menu.$panel);

    var $backPanel = $('<div class="main-menu-back-panel"></div>')
        .appendTo($header);

    var $titlePanel = $('<div class="main-menu-title">众核脑图</div>')
        .appendTo($header);

    var $backButton = new FUI.Button({

        className: 'main-menu-back-button',
        label: minder.getLang('ui.back')

    }).appendTo($backPanel[0]).on('click', $menu.hide);

    $menu.on('show', function() {
        var $title = minder.getUI('topbar/title');
        $titlePanel.text($title.getTitle());
    });

    return $header;
});
/**
 * @fileOverview
 *
 * 菜单默认选择项目
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('menu/default', function(minder) {

    minder.on('uiready', function() {

        var $menu = minder.getUI('menu/menu');
        var $open = minder.getUI('menu/open/open');
        var $recent = minder.getUI('menu/open/recent');
        var $save = minder.getUI('menu/save/save');
        var $share = minder.getUI('menu/share/share');
        var $draft = minder.getUI('menu/open/draft');

        setMenuDefaults();
        
        // $menu.show();
        // $menu.$tabs.select(1);
        // $open.$tabs.select(1);
        // return;

        loadLandingFile();
        function setMenuDefaults() {

            // 主菜单默认选中「打开」
            $menu.$tabs.select(1);

            // 打开菜单默认选中「本地文件」
            $open.$tabs.select(2);

            // 保存菜单默认选中「导出到本地」
            $save.$tabs.select(1);

            // 如果用户登陆了，选中「百度云存储」
            fio.user.check().then(function(user) {
                if (user) {
                    $save.$tabs.select(0);
                }
            });

            $share.$menu.$tabs.select(0); // 当前脑图
        }

        function loadLandingFile() {
            var pattern = /(?:shareId|share_id)=(\w+)([&#]|$)/;
            var match = pattern.exec(window.location) || pattern.exec(document.referrer);

            if (match) {
                return $share.loadShareFile();
            }

            // 检查登录状态
            fio.user.check().then(function(user) {
                var draft = $draft.last();
                var recent = $recent.last();

                // 登录
                if (user) {
                    if (recent) {
                        if (draft) {
                            if (recent.time > draft.time) openRecent();
                            else openDraft();
                        } else {
                            openRecent();
                        }
                    } else {
                        if (draft) openDraft();
                        else $open.$tabs.select(1); // locale netdisk
                    }
                } else {
                    if (draft) openDraft();
                    else $open.$tabs.select(2); // locale local
                }

                function openDraft() {
                    $open.$tabs.select(3);
                    $draft.openLast();
                }

                function openRecent() {
                    $open.$tabs.select(0);
                    $recent.loadLast();
                }
            });
        }
    });
});
/**
 * @fileOverview
 *
 * 新建文件菜单
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/new/new', function(minder) {

    var $menu = minder.getUI('menu/menu');
    var $doc = minder.getUI('doc');
    var ret = minder.getUI('eve').setup({});

    var $panel = $menu.createSub('new');

    // 模板列表容器
    var $ul = $('<ul></ul>')
        .addClass('new-file-template-select')
        .appendTo($panel);

    // 模板容器
    var $li;

    var templates = KityMinder.getTemplateList();

    for (var name in templates) {
        $li = $('<li></li>')
            .addClass('template-item')
            .addClass(name)
            .data('template', name)
            .append('<a>' + minder.getLang('template')[name] + '</a>')
            .appendTo($ul);
    }

    $ul.delegate('.template-item', 'click', function(e) {

        if (!$doc.checkSaved()) return;

        var template = $(e.target).data('template');
        $doc.load({
            content: {
                template: template,
                version: KityMinder.version,
                data: {
                    text: minder.getLang('template')[template]
                }
            },
            protocol: null,
            saved: true
        });
        $menu.hide();
    });

    return ret;
});
/**
 * @fileOverview
 *
 * 打开菜单（二级菜单）
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('menu/open/open', function(minder) {
    return minder.getUI('menu/menu').createSubMenu('open', true);
});
/**
 * @fileOverview
 *
 * 最近文件功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/open/recent', function(minder) {

    var $menu = minder.getUI('menu/menu');
    var $open = minder.getUI('menu/open/open');
    var $loader = minder.getUI('widget/fileloader');
    var frdTime = minder.getUI('widget/friendlytimespan');
    var doc = minder.getUI('doc');
    var recentList = minder.getUI('widget/locallist').use('recent');
    var finder = minder.getUI('widget/netdiskfinder');

    /* 网盘面板 */
    var $panel = $($open.createSub('recent')).addClass('recent-file-panel');

    minder.on('uiready', function() {
        minder.getUI('topbar/user').requireLogin($panel);
    });

    /* 标题 */
    var $title = $('<h2></h2>')
        .text(minder.getLang('ui.recent'))
        .appendTo($panel);

    var $clear = $('<button></button>')
        .addClass('clear-recent-list')
        .text(minder.getLang('ui.clearrecent'))
        .appendTo($panel);

    /* 最近文件列表容器 */
    var $ul = $('<ul></ul>')
        .addClass('recent-file-list')
        .appendTo($panel);

    $ul.delegate('.recent-file-item', 'click', function(e) {

        if (!doc.checkSaved()) return;

        var netdisk = minder.getUI('menu/open/netdisk');
        var path = $(e.target)
            .closest('.recent-file-item')
            .data('path');

        netdisk.open(path);
    });

    $clear.on('click', function() {
        if (!window.confirm(minder.getLang('ui.clearrecentconfirm'))) return;
        recentList.clear();
        renderList();
    });

    doc.on('docload', addToList);
    doc.on('docsave', addToList);

    finder.on('mv', trackFileMove);

    renderList();

    function trackFileMove(from, to) {
        var fromPath = from.split('/');
        var toPath = to.split('/');

        function preCommonLength(a, b) {
            var i = 0;
            while((i in a) && (i in b) && a[i] == b[i]) i++;
            return (i in b) ? 0 : i;
        }

        recentList.forEach(function(item) {
            var originPath = item.path.split('/');
            var clen = preCommonLength(originPath, fromPath);
            if (clen) {
                var movedPath = toPath.concat(originPath.slice(clen));
                item.path = movedPath.join('/');
                item.filename = toPath.pop();
            }
        });

        renderList();
    }

    function addToList(doc) {

        if (doc.source != 'netdisk') return;

        var exist = recentList.findIndex('path', doc.path);

        if (~exist) {
            recentList.remove(exist);
        }

        recentList.unshift({
            path: doc.path,
            filename: fio.file.anlysisPath(doc.path).filename,
            title: minder.getMinderTitle(),
            time: +new Date()
        });

        renderList();
    }

    function renderList() {
        $ul.empty();

        recentList.forEach(function(item) {

            var $li = $('<li></li>')
                .addClass('recent-file-item')
                .data('path', item.path)
                .appendTo($ul);

            $('<h4></h4>')
                .addClass('file-name')
                .text(item.filename)
                .appendTo($li);

            $('<p></p>')
                .addClass('file-title')
                .text(item.title)
                .appendTo($li);

            $('<span></span>')
                .addClass('file-time')
                .displayFriendlyTime(item.time)
                .appendTo($li);
        });
    }

    return {
        hasRecent: function() {
            return recentList.length;
        },
        loadLast: function() {
            $ul.find('.recent-file-item').eq(0).click();
        },
        last: function() {
            return recentList.get(0) || null;
        }
    };

});
/**
 * @fileOverview
 *
 * 支持从百度网盘打开文件
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/open/netdisk', function(minder) {

    var $menu = minder.getUI('menu/menu');
    var $open = minder.getUI('menu/open/open');
    var $netdiskfinder = minder.getUI('widget/netdiskfinder');
    var $eve = minder.getUI('eve');
    var $doc = minder.getUI('doc');
    var ret = $eve.setup({});
    var notice = minder.getUI('widget/notice');

    /* 网盘面板 */
    var $panel = $($open.createSub('netdisk')).addClass('netdisk-open-panel');

    /* extension => protocol */
    var supports = {};

    minder.getSupportedProtocols().forEach(function(protocol) {
        if (protocol.decode) {
            supports[protocol.fileExtension] = protocol;
        }
    });

    /* Finder */
    var $finder = $netdiskfinder.generate($panel, function(file) {

        return supports[file.extension];

    });

    $finder.on('fileclick', function(file) {
        if (!$doc.checkSaved()) return;
        return open(file.path);
    });

    function open(path, errorHandler) {

        $menu.hide();

        $(minder.getRenderTarget()).addClass('loading');

        var info = fio.file.anlysisPath(path);
        var protocol = supports[info.extension];

        function read() {
            return fio.file.read({

                path: path,
                dataType: protocol.dataType

            });
        }

        function load(file) {

            var doc = {
                protocol: supports[file.extension].name,
                content: file.data.content,
                title: file.filename,
                source: 'netdisk',
                path: file.path,
                saved: true
            };

            return $doc.load(doc);
        }

        function error(e) {
            return errorHandler && errorHandler(e) || notice.error('err_load', e);
        }

        return read().then(load)['catch'](error).then(function() {

            $(minder.getRenderTarget()).removeClass('loading');

        });

    }

    ret.open = open;

    return ret;
});
/**
 * @fileOverview
 *
 * 支持从本地打开文件
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/open/local', function(minder) {

    var $menu = minder.getUI('menu/menu');
    var $open = minder.getUI('menu/open/open');
    var $doc = minder.getUI('doc');
    var notice = minder.getUI('widget/notice');

    /* extension => protocol */
    var supports = {};
    var accepts = [];

    minder.getSupportedProtocols().forEach(function(protocol) {
        if (protocol.decode) {
            supports[protocol.fileExtension] = protocol;
            accepts.push(protocol.fileExtension);
        }
    });

    /* 网盘面板 */
    var $panel = $($open.createSub('local')).addClass('local-file-open-panel');

    /* 标题 */
    $('<h2>本地文件</h2>')
        .appendTo($panel);

    /* 选择文件 */
    var $pick = $('<div class="pick-file"></div>')
        .appendTo($panel);

    var $pickButton = $('<a></a>')
        .text(minder.getLang('ui.pickfile'))
        .appendTo($pick);

    $('<span></span>')
        .text(minder.getLang('ui.acceptfile', accepts.map(function(ext) {

            var protocol = supports[ext];
            return protocol.fileDescription + '(' + ext + ')';

        }).join(', '))).appendTo($pick);

    /* 拖放提示 */
    var $drop = $('<div class="drop-file"></div>')
        .append($('<span></span>').html(minder.getLang('ui.dropfile')))
        .appendTo($panel);


    /* 交互事件 */
    $pickButton.click(function() {
        if (!$doc.checkSaved()) return;
        $('<input type="file" />')
            .attr('accept', accepts.join())
            .on('change', function(e) {
                read(this.files[0]);
                $menu.hide();
            }).click();
    });

    var cwrapper = $('#content-wrapper')[0];
    cwrapper.addEventListener('dragover', function(e) {
        e.preventDefault();
        e.stopPropagation();
    }, false);

    cwrapper.addEventListener('drop', function(e) {
        if (e.dataTransfer.files.length) {
            e.preventDefault();
            if (!$doc.checkSaved()) return;
            read(e.dataTransfer.files[0]);
            $menu.hide();
        }
    }, false);

    function read(domfile) {
        if (!domfile) return;

        var info = new fio.file.anlysisPath(domfile.name);
        var protocol = supports[info.extension];

        if (!protocol || !protocol.decode) {
            notice.warn(minder.getLang('ui.unsupportedfile'));
            return Promise.reject();
        }

        function loadFile(file, protocol) {
            return new Promise(function(resolve, reject) {
                var reader;

                if (protocol.dataType == 'blob') {

                    resolve(new fio.file.Data(domfile));

                } else {

                    reader = new FileReader();
                    reader.onload = function() {
                        resolve(new fio.file.Data(this.result));
                    };
                    reader.onerror = reject;
                    reader.readAsText(domfile, 'utf-8');
                }
            });
        }

        function loadFileError() {
            var notice = minder.getUI('widget/notice');
            notice.error('err_localfile_read');
        }

        function loadDoc(data) {
            var doc = {
                content: data.content,
                protocol: protocol.name,
                title: info.filename,
                source: 'local'
            };

            return $doc.load(doc);
        }

        $(minder.getRenderTarget()).addClass('loading');

        return loadFile(domfile, protocol).then(loadDoc, loadFileError).then(function() {

            $(minder.getRenderTarget()).removeClass('loading');

        });
    }

    return {
        read: read
    };
});
/**
 * @fileOverview
 *
 * 草稿箱功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/open/draft', function(minder) {
    var $menu = minder.getUI('menu/menu');
    var $open = minder.getUI('menu/open/open');
    var $loader = minder.getUI('widget/fileloader');

    var frdTime = minder.getUI('widget/friendlytimespan');

    var $doc = minder.getUI('doc');

    // 旧数据迁移
    if (localStorage.drafts) {
        var oldDrafts = JSON.parse(localStorage.drafts);
        var list = oldDrafts.map(function(draft) {
            var ret = {};
            ret.json = draft.data;
            ret.time = +new Date(draft.update);
            ret.title = JSON.parse(draft.data).data.text;
            return ret;
        });
        delete localStorage.drafts;
        localStorage.draft = JSON.stringify(list);
    }

    var draftList = minder.getUI('widget/locallist').use('draft');

    /* 网盘面板 */
    var $panel = $($open.createSub('draft')).addClass('draft-panel');

    /* 标题 */
    var $title = $('<h2></h2>')
        .text(minder.getLang('ui.menu.draftheader'))
        .appendTo($panel);

    var $clear = $('<button></button>')
        .addClass('clear-draft')
        .text(minder.getLang('ui.cleardraft'))
        .appendTo($panel);

    /* 最近文件列表容器 */
    var $ul = $('<ul></ul>')
        .addClass('draft-list')
        .appendTo($panel);

    var current = null,
        lastDoc = null;

    $ul.delegate('.draft-list-item', 'click', function(e) {

        if (!$doc.checkSaved()) return;

        var item = $(e.target).closest('.draft-list-item').data('item');

        var index = draftList.findIndex(function(finding) {
            return finding == item;
        });

        if (index > -1) {
            current = item;

            draftList.remove(index);
            draftList.unshift(current);

            lastDoc = {
                title: current.title,
                protocol: 'json',
                content: current.json,
                path: current.path,
                source: current.source,
                saved: false
            };

            $doc.load(lastDoc);
        }
        $menu.hide();
    });

    $clear.on('click', function() {
        if (!window.confirm(minder.getLang('ui.cleardraftconfirm'))) return;
        draftList.clear();
        current = null;
        renderList();
    });

    $doc.on('docsave', popDraft);
    $doc.on('docchange', pushDraft);

    renderList();

    function pushDraft(doc) {
        if (doc.saved) return;
        if (doc == lastDoc) {
            if (current) {
                draftList.remove(0);
            }
        } else {
            current = null;
        }
        lastDoc = doc;
        current = current || {};
        current.json = JSON.stringify(minder.exportJson());
        current.title = doc.title;
        current.time = +new Date();
        current.path = doc.path;
        current.source = doc.source;
        draftList.unshift(current);
        renderList();
    }

    function popDraft() {
        if (current) {
            draftList.remove(0);
            current = null;
        }
        renderList();
    }

    function renderList() {
        $ul.empty();

        draftList.forEach(function(item) {

            var $li = $('<li></li>')
                .addClass('draft-list-item')
                .data('item', item)
                .appendTo($ul);

            $('<h4></h4>')
                .addClass('draft-title')
                .text(item.title)
                .appendTo($li);

            $('<span></span>')
                .addClass('file-time')
                .displayFriendlyTime(item.time)
                .appendTo($li);
        });
    }

    return {
        hasDraft: function() {
            return draftList.length;
        },
        openLast: function() {
            $ul.find('.draft-list-item').eq(0).click();
        },
        last: function() {
            return draftList.get(0) || null;
        }
    };
});
/**
 * @fileOverview
 *
 * 保存菜单（二级菜单）
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/save/save', function(minder) {
    return minder.getUI('menu/menu').createSubMenu('save');
});
/**
 * @fileOverview
 *
 * 保存文件到网盘的功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('menu/save/netdisk', function(minder) {
    var $menu = minder.getUI('menu/menu');
    var $save = minder.getUI('menu/save/save');
    var $netdiskfinder = minder.getUI('widget/netdiskfinder');
    var $eve = minder.getUI('eve');
    var $doc = minder.getUI('doc');
    var ret = $eve.setup({});
    var notice = minder.getUI('widget/notice');

    /* extension => protocol */
    var supports = {};

    minder.getSupportedProtocols().forEach(function(protocol) {
        if (protocol.encode && protocol.decode) {
            supports[protocol.fileExtension] = protocol;
        }
    });

    // 删除不稳定两种格式
    delete supports['.mm'];
    delete supports['.xmind'];

    /* 网盘面板 */
    var $panel = $($save.createSub('netdisk', true)).addClass('netdisk-save-panel');

    var $finder = $netdiskfinder.generate($panel, function(file) {
        return supports[file.extension];
    });

    var $selects = $('<div class="netdisk-save-select"></div>')
        .appendTo($panel);

    $('<label>')
        .text(minder.getLang('ui.saveas'))
        .appendTo($selects);

    /* 文件名 */
    var $filename = $('<input>')
        .addClass('fui-widget fui-selectable')
        .attr('type', 'text')
        .attr('placeholder', minder.getLang('ui.filename'))
        .attr('title', minder.getLang('ui.filename'))
        .on('keydown', function(e) {
            if (e.keyCode == 27) $menu.toggle();
            if (e.keyCode == 13) save();
        })
        .appendTo($selects);

    /* 文件格式 */
    var $format = $('<select>')
        .attr('title', minder.getLang('ui.fileformat'))
        .appendTo($selects);

    for (var ext in supports) {
        var protocol = supports[ext];
        if (!protocol.encode) return;
        $('<option>')
            .text(protocol.fileDescription + '(' + protocol.fileExtension + ')')
            .val(ext)
            .appendTo($format);
    }

    $format.val('.km');

    $format.on('change', normalizeFilename);

    /* 保存按钮 */
    var $saveBtn = $('<button></button>')
        .addClass('save-button')
        .text(minder.getLang('ui.save'))
        .click(save)
        .appendTo($selects);

    $menu.on('show', setFilename);

    $finder.on('fileclick', function(file) {
        $finder.select(file.path);
        $filename.val(file.filename);
    });

    ret.quickSave = quickSave;

    window.onbeforeunload = function() {
        var noask = ret.mute || window.location.href.indexOf('noask') > 0;
        if (!$doc.checkSaved(true) && !noask)
            return minder.getLang('ui.unsavedcontent', '* ' + $doc.current().title);
    };

    var autoSaveDuration = minder.getOptions('autoSave');

    if (autoSaveDuration !== false) {
        autoSaveDuration = isNaN(autoSaveDuration) ? 3000 : (autoSaveDuration * 1000);
        autoSave();
    }

    var autoSaveTimer = 0;

    function autoSave() {
        function lazySave(doc) {
            if (doc.saved) return;
            clearTimeout(autoSaveTimer);
            autoSaveTimer = setTimeout(saveCurrent, autoSaveDuration);
        }
        $doc.on('docchange', lazySave);
    }

    // 快速保存
    function quickSave() {
        var doc = $doc.current();
        if (doc.source != 'netdisk' && !$menu.isVisible()) {
            $menu.$tabs.select(2);
            $save.$tabs.select(0);
            return $menu.show();
        } else {
            saveCurrent();
        }
    }

    function saveCurrent() {
        var doc = $doc.current();

        if (doc.source != 'netdisk') return Promise.resolve();

        var $title = minder.getUI('topbar/title').$title;
        $filename.val(doc.title);
        return doSave(doc.path, doc.protocol, doc, $title, 'leaveTheMenu');
    }

    function normalizeFilename() {
        var filename = $filename.val();
        var info = fio.file.anlysisPath(filename);
        var ext = info.extension;

        if (ext != $format.val()) {
            if (ext in supports) {
                $filename.val(info.name + $format.val());
            } else {
                $filename.val(filename + $format.val());
            }
            $filename[0].select();
        }

        return $filename.val();
    }

    function getSaveContext() {
        var filename = normalizeFilename();

        var path = $finder.pwd() + filename;
        var doc = $doc.current();
        var protocol = supports[$format.val()];

        var exist = $finder.select(path); // 目标路径存在
        var match = doc.path == path; // 目标路径正是当前文档
        var duplicated = exist && !match;

        return {
            filename: filename,
            path: path,
            doc: doc,
            protocol: protocol,
            exist: exist,
            match: match,
            duplicated: duplicated
        };
    }

    function save() {
        var ctx = getSaveContext();

        if (ctx.match || !ctx.exist ||
            ctx.duplicated && window.confirm(minder.getLang('ui.overrideconfirm', ctx.filename))) {
            doSave(ctx.path, ctx.protocol.name, ctx.doc, $panel);
        }
    }

    var saving = 0;

    function doSave(path, protocol, doc, $mask, leaveTheMenu, msg) {

        if (saving) return;

        saving = true;
        $doc.lock();

        if ($mask) $mask.addClass('loading');

        function upload(data) {
            return fio.file.write({
                path: path,
                content: data,
                ondup: fio.file.DUP_OVERWRITE
            });
        }

        function finish(file) {

            if (!file.modifyTime) throw new Error('File Save Error');

            if (!leaveTheMenu) {
                $menu.hide();
            }

            doc.path = file.path;
            doc.title = file.filename;
            doc.source = 'netdisk';
            doc.protocol = protocol;

            $doc.save(doc);
            $doc.unlock();

            //notice.info(msg || minder.getLang('ui.save_success', doc.title, file.modifyTime.toLocaleTimeString()));

            setTimeout(function() {
                $finder.list($finder.pwd(), true);
            }, 1499);

        }

        function error(e) {
            notice.error('err_save', e);
        }

        return minder.exportData(protocol).then(upload).then(finish, error).then(function() {
            if ($mask) $mask.removeClass('loading');
            saving = false;
        });
    }

    function setFilename() {
        var doc = $doc.current();

        switch (doc.source) {
            case 'netdisk':
                setFilenameForNetDiskSource(doc);
                break;
            default:
                setFilenameForOtherSource(doc);
                break;
        }

        $filename[0].select();
    }

    function setFilenameInputValue(filename) {
        $filename.val(filename);
        normalizeFilename(filename);
    }

    function setFilenameForNetDiskSource(doc) {
        if (!fio.user.current()) return;

        var path = doc.path;
        var pathInfo = fio.file.anlysisPath(path);

        // 选中当前文件
        if ($finder.pwd() != pathInfo.parentPath) {
            $finder.list(pathInfo.parentPath).then(function() {
                $finder.select(path);
            });
        } else {
            $finder.select(path);
        }

        setFilenameInputValue(pathInfo.filename);
    }

    function setFilenameForOtherSource(doc) {
        setFilenameInputValue(doc.title);
        $finder.select(null);
    }

    return ret;
});
/**
 * @fileOverview
 *
 * 导出数据到本地
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('menu/save/download', function(minder) {
    var $menu = minder.getUI('menu/menu');
    var $save = minder.getUI('menu/save/save');

    /* 导出面板 */
    var $panel = $($save.createSub('download')).addClass('download-panel');

    /* 标题 */
    var $title = $('<h2></h2>')
        .text(minder.getLang('ui.menu.downloadheader'))
        .appendTo($panel);

    var $list = $('<ul>')
        .addClass('download-list')
        .appendTo($panel);

    var supports = [];

    minder.getSupportedProtocols().forEach(function(protocol) {
        if (protocol.encode) {
            supports.push(protocol);
        }
    });

    supports.forEach(function(protocol) {
        $('<li>')
            .addClass(protocol.name)
            .text(protocol.fileDescription + ' (' + protocol.fileExtension + ')')
            .data('protocol', protocol)
            .appendTo($list);
    });

    $list.delegate('li', 'click', function(e) {
        var protocol = $(e.target).data('protocol');
        if (!$panel.hasClass('loading')) doExport(protocol);
    });

    function doExport(protocol) {
        var filename = minder.getMinderTitle() + protocol.fileExtension;
        var mineType = protocol.mineType || 'text/plain';

        $panel.addClass('loading');

        var options = {
            download: true,
            filename: filename
        };

        minder.exportData(protocol.name, options).then(function(data) {

            if (protocol.name == 'freemind') return;

            switch (protocol.dataType) {
                case 'text':
                    return doDownload(buildDataUrl(mineType, data), filename, 'text');
                case 'base64':
                    return doDownload(data, filename, 'base64');
                case 'blob':
                    return null;
            }

            return null;

        })['catch'](function exportError(e) {
            var notice = minder.getUI('widget/notice');
            return notice.error('err_download', e);
        })

        .then(function done(tick) {
            $panel.removeClass('loading');
        });
    }
    function doDownload(url, filename, type) {
        var stamp = +new Date() * 1e5 + Math.floor(Math.random() * (1e5 - 1));

        stamp = stamp.toString(36);

        var ret = new Promise(function(resolve, reject) {
            var ticker = 0;
            var MAX_TICK = 30;
            var interval = 1000;

            function check() {
                if (document.cookie.indexOf(stamp + '=1') != -1) return resolve([stamp, ticker]);
                if (++ticker > MAX_TICK) {
                    resolve([stamp, ticker]);
                }
                setTimeout(check, interval);
            }

            setTimeout(check, interval);
        });

        var content = url.split(',')[1];

        var $form = $('<form></form>').attr({
            'action': 'download.php',
            'method': 'POST',
            'accept-charset': 'utf-8'
        });

        var $content = $('<input />').attr({
            name: 'content',
            type: 'hidden',
            value: decodeURIComponent(content)
        }).appendTo($form);

        var $type = $('<input />').attr({
            name: 'type',
            type: 'hidden',
            value: type
        }).appendTo($form);

        var $filename = $('<input />').attr({
            name: 'filename',
            type: 'hidden',
            value: filename
        }).appendTo($form);

        if (kity.Browser.ie) {
            $('<input name="iehack" value="1" />').appendTo($form);
        }
        $('<input name="stamp" />').val(stamp).appendTo($form);

        var netdisk = minder.getUI('menu/save/netdisk');
        if (netdisk) {
            netdisk.mute = true;
            setTimeout(function() {
                netdisk.mute = false;
            }, 1000);
        }

        $form.appendTo('body').submit().remove();

        return ret;
    }

    function buildDataUrl(mineType, data) {
        return 'data:' + mineType + '; utf-8,' + encodeURIComponent(data);
    }
});
/**
 * @fileOverview
 *
 * 分享功能交互
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('menu/share/share', function(minder) {
    var $share_menu = minder.getUI('menu/menu').createSubMenu('share');
    var $create_menu = $($share_menu.createSub('createshare'));
    var $manage_menu = $($share_menu.createSub('manageshare'));
    var $share_list = $('<ul>')
        .attr('id', 'manage-share-list')
        .appendTo($manage_menu);
    var $doc = minder.getUI('doc');
    var notice = minder.getUI('widget/notice');
    var finder = minder.getUI('widget/netdiskfinder');

    var BACKEND_URL = 'http://naotu.baidu.com/share.php';

    if (window.location.host == 'local.host') {
        //BACKEND_URL = '/naotu/share.php'; // 测试环境
    }

    var currentShare = null;
    var shareList = [];

    renderCreatePanel().then(bindCreatePanelEvent);
    renderManagePanel();

    var shareListLoaded = loadShareList();

    shareListLoaded.then(renderShareList);
    shareListLoaded.then(bindManageActions);

    minder.on('uiready', function() {
        minder.getUI('topbar/user').requireLogin($manage_menu);
    });

    $doc.on('docload', function(doc) {
        if (doc.source != 'netdisk') {
            currentShare = null;
            setShareType('none');
        }
        shareListLoaded.then(function() {
            var shared = getShareByPath(doc.path);
            setCurrentShare(shared);
        });
    });

    $doc.on('docsave', function(doc) {
        if (doc.source != 'netdisk') return;
        var shared = getShareByPath(doc.path);

        if (shared) {
            fio.user.check().then(function(user) {
                $.pajax({
                    url: BACKEND_URL,
                    type: 'POST',
                    data: {
                        action: 'update',
                        ak: user.access_token,
                        id: shared.id || shared.shareMinder.id,
                        record: doc.json
                    }
                }).then(function() {
                    //notice.info(minder.getLang('ui.share_sync_success', doc.title));
                })['catch'](function(e) {
                    notice.error('err_share_sync_fail', e);
                });
            });
        }
    });

    finder.on('mv', trackFileMove);

    function trackFileMove(from, to) {
        var fromPath = from.split('/');
        var toPath = to.split('/');

        function preCommonLength(a, b) {
            var i = 0;
            while ((i in a) && (i in b) && a[i] == b[i]) i++;
            return (i in b) ? 0 : i;
        }

        shareListLoaded.then(function(list) {
            var userChecked = fio.user.check();
            list.forEach(function(item) {
                var originPath = item.path.split('/');
                var clen = preCommonLength(originPath, fromPath);
                if (clen) {

                    var movedPath = toPath.concat(originPath.slice(clen));

                    userChecked.then(function(user) {
                        $.pajax({
                            url: BACKEND_URL,
                            type: 'POST',
                            data: {
                                action: 'move',
                                ak: user.access_token,
                                id: item.id || item.shareMinder.id,
                                path: movedPath.join('/')
                            }
                        }).then(function() {
                            notice.info(minder.getLang('ui.share_sync_success', item.title));
                        })['catch'](function(e) {
                            notice.error('err_share_sync_failed', e);
                        });
                    });
                    item.path = movedPath.join('/');
                }
            });
            renderShareList(list);
        });
    }

    function loadShareFile() {

        var pattern = /(?:shareId|share_id)=(\w+)([&#]|$)/;
        var match = pattern.exec(window.location) || pattern.exec(document.referrer);

        if (!match) return Promise.resolve(null);

        var shareId = match[1];

        $(minder.getRenderTarget()).addClass('loading');

        shareListLoaded.then(function(list) {
            for (var i = 0; i < list.length; i++) {
                var id = list[i].id || (list[i].shareMinder && list[i].shareMinder.id);
                if (id == shareId && list[i].path) {
                    return loadOriginFile(list[i]);
                }
            }
            return loadShare(shareId);

        });

    }

    function loadOriginFile(share) {

        var $netdisk = minder.getUI('menu/open/netdisk');
        notice.info(minder.getLang('ui.load_share_for_edit', share.title));
        return $netdisk.open(share.path, function() {
            // 网盘加载失败
            return loadShare(share);
        });
    }

    function loadShare(shareId) {

        function renderShareData(data) {

            if (data.error) {
                notice.error('err_share_data', data.error);
                return;
            }

            var content = data.shareMinder.data;

            return $doc.load({

                source: 'share',
                content: content,
                protocol: 'json',
                saved: true,
                ownerId: data.uid,
                ownerName: data.uname

            });
        }

        var $container = $(minder.getRenderTarget()).addClass('loading');

        return $.pajax({

            url: 'http://naotu.baidu.com/share.php',

            data: {
                action: 'find',
                id: shareId
            },

            dataType: 'json'

        }).then(renderShareData)['catch'](function(e) {

            notice.error('err_share_data', e);

        }).then(function() {
            $container.removeClass('loading');
        });
    }

    function getShareByPath(path) {
        if (!path || !shareList) return null;

        var i = shareList.length;
        while (i--) {
            if (shareList[i].path == path) return shareList[i];
        }
        return null;
    }

    function setCurrentShare(share) {
        currentShare = share;
        if (share)
            renderPublicShare(share);
        else
            setShareType('none');
    }

    function renderCreatePanel() {
        // render template
        return $.pajax({ url: 'static/pages/createshare.html' }).then(function(html) {
            /* global jhtmls: true */
            var render = jhtmls.render(html);
            $create_menu.html(render({
                lang: minder.getLang('ui'),
                minder: minder
            }));
            setTimeout(zeroCopy, 10);
            return $create_menu;
        });
    }

    function renderManagePanel() {
        $manage_menu.append($('<h2>')
            .text(minder.getLang('ui.manage_share'))
            .attr('id', 'manage-share-header'));
    }

    function bindCreatePanelEvent($panel) {
        $panel.delegate('input[name=sharetype]', 'click', function(e) {
            var actions = {
                'none': removeCurrentShare,
                'public': createPublicShare
            };
            actions[e.target.value]();
        });

        $panel.delegate('input#share-url', 'dblclick', function() {
            this.select();
        });

        $panel.delegate('#copy-share-url', 'click', function() {
            if (kity.Browser.safari && kity.Browser.safari < 8) {
                var input = $('#share-url');
                input.focus();
                input.select();
                window.alert(minder.getLang('ui.clipboardunsupported'));
            }
        });
    }

    function bindManageActions() {
        $manage_menu.delegate('.share-item a', 'click', function(e) {
            var $target = $(e.target);
            var $li = $target.closest('.share-item');
            var share = $li.data('share');

            switch (true) {
                case $target.hasClass('view-action'):
                    window.open(buildShareUrl(share.id || share.shareMinder.id), '_blank');
                    return;

                case $target.hasClass('remove-action'):

                    $li.addClass('loading');

                    removeShare(share).then(function(result) {

                        if (result && result.deleted) {
                            shareList.splice(shareList.indexOf(share), 1);
                            $li.slideUp(function() {
                                $li.remove();
                            });
                            if (share == currentShare) {
                                removeCurrentShareSelect();
                            }
                        }

                    }).then(function() {

                        $li.removeClass('loading');

                    });

                    return;

                case $target.hasClass('edit-action'):
                    loadOriginFile(share);
                    return;
            }
        });
    }

    function removeShare(share) {
        return fio.user.check().then(function(user) {
            return $.pajax(BACKEND_URL, {

                type: 'POST',
                data: {
                    action: 'remove',
                    ak: user && user.access_token,
                    id: share.id || share.shareMinder.id
                },
                dataType: 'json'

            })['catch'](function(e) {
                var notice = minder.getUI('widget/notice');
                notice.error('err_remove_share', e);
            });
        });
    }

    function removeCurrentShare() {
        if (!currentShare) return;

        $create_menu.addClass('loading');

        return removeShare(currentShare).then(function(result) {
            if (result && result.deleted) {
                removeCurrentShareSelect();
            }
            $create_menu.removeClass('loading');
        });
    }

    function removeCurrentShareSelect() {
        if (currentShare.$listItem) {
            currentShare.$listItem.remove();
        }
        currentShare = null;
        setShareType('none');
    }

    function uuid() {
        // 最多使用 1e7，否则 IE toString() 会出来指数表示法
        var timeLead = 1e6;
        return ((+new Date() * timeLead) + (Math.random() * --timeLead)).toString(36);
    }

    function createPublicShare(user) {
        if (currentShare) return;

        $create_menu.addClass('loading');

        return fio.user.check().then(function(user) {

            var record = {
                shareMinder: {
                    id: uuid(),
                    data: JSON.stringify(minder.exportJson())
                }
            };

            var currentDoc = $doc.current();

            if (currentDoc.source == 'netdisk') {
                record.path = currentDoc.path;
            }

            return $.pajax(BACKEND_URL, {

                type: 'POST',
                data: {
                    action: 'insert',
                    record: JSON.stringify(record),
                    ak: user && user.access_token
                },
                dataType: 'json'

            }).then(function(result) {

                if (result.error) {
                    throw new Error(result.error);
                }

                return result;

            })['catch'](function(e) {

                var notice = minder.getUI('widget/notice');
                notice.error('err_create_share', e);

            });
        })

        .then(function(shared) {
            if (shared) {
                setCurrentShare(shared);
                shareListLoaded.then(function() {
                    shareList.unshift(shared);
                    $('#manage-share-list').prepend(currentShare.$listItem = buildShareItem(shared));
                });
            }
            $create_menu.removeClass('loading');
        });
    }

    function buildShareUrl(id) {

        var baseUrl = /^(.*?)(\?|\#|$)/.exec(window.location.href)[1];

        baseUrl = baseUrl.split('edit.html')[0];

        return baseUrl + 'viewshare.html?shareId=' + id;
    }

    function setShareType(value) {
        var $sbody = $('#public-share .share-body', $create_menu);
        if (value == 'public') {
            $sbody.show();
        } else {
            $sbody.hide();
        }
        $('#share-select input[name=sharetype][value=' + value + ']', $create_menu)
            .prop('checked', true);
    }

    function renderPublicShare(shared) {
        var $sbody = $('#public-share .share-body', $create_menu);

        var shareUrl = buildShareUrl(shared.id || shared.shareMinder.id);

        $('#share-url', $sbody).val(shareUrl)[0].select();

        // qr code
        var $qrcontainer = $sbody.find('.share-qr-code').empty();

        new window.QRCode($qrcontainer[0], {
            text: shareUrl,
            width: 128,
            height: 128,
            correctLevel : window.QRCode.CorrectLevel.M
        });

        var shareConfig = window._bd_share_config && window._bd_share_config.common,
            resetShare = window._bd_share_main && window._bd_share_main.init;

        if (shareConfig && resetShare) {
            shareConfig.bdTitle = shareConfig.bdText = minder.getMinderTitle();
            shareConfig.bdDesc = shareConfig.bdText = minder.getLang('ui.sns_share_text',
                minder.getMinderTitle(), shareUrl);
            shareConfig.bdUrl = shareUrl;
            resetShare();
        }
        setShareType('public');
    }

    function loadShareList() {

        return fio.user.check().then(function(user) {
            if (!user) return;
            return $.pajax(BACKEND_URL, {

                type: 'GET',

                data: {
                    action: 'list',
                    ak: user && user.access_token
                },

                dataType: 'json'

            }).then(function(result) {

                return (shareList = result.list || []);

            });
        });
    }

    function renderShareList(list) {
        var frdTime = minder.getUI('widget/friendlytimespan');
        if (!list) return;
        $share_list.empty();
        list.forEach(function(share) {
            $share_list.append(share.$listItem = buildShareItem(share));
        });
    }

    function buildShareItem(share) {
        var id = share.id || (share.shareMinder && share.shareMinder.id);
        if (!id) return;

        var $li = $('<li>')
            .addClass('share-item')
            .data('share', share);

        $('<span>')
            .addClass('title')
            .text(share.title)
            .appendTo($li);

        $('<span>')
            .addClass('url')
            .text(share.path ?
                share.path.replace('/apps/kityminder', minder.getLang('ui.mydocument')) :
                buildShareUrl(id))
            .appendTo($li);

        if (share.ctime) {
            $('<span>')
                .addClass('ctime')
                .displayFriendlyTime(+share.ctime)
                .appendTo($li);
        }

        $('<a>')
            .addClass('remove-action')
            .text(minder.getLang('ui.share_remove_action'))
            .attr('title', minder.getLang('ui.share_remove_action'))
            .appendTo($li);

        $('<a>')
            .addClass('view-action')
            .text(minder.getLang('ui.share_view_action'))
            .attr('title', minder.getLang('ui.share_view_action'))
            .appendTo($li);

        if (share.path)
            $('<a>')
                .addClass('edit-action')
                .text(minder.getLang('ui.share_edit_action'))
                .attr('title', minder.getLang('ui.share_edit_action'))
                .appendTo($li);

        return $li;
    }

    function clearShareList() {
        shareList = [];
    }

    function shareRedirect() {
        var pattern = /(?:shareId|share_id)=(\w+)([&#]|$)/;
        var match = pattern.exec(window.location) || pattern.exec(document.referrer);

        if (match) {
            window.location.href = 'viewshare.html?shareId=' + match[1];
        }
    }

    function zeroCopy() {

        /* global ZeroClipboard:true */

        var $copy_url_btn = $('#copy-share-url', $create_menu);

        if (window.ZeroClipboard) {
            ZeroClipboard.config({
                swfPath: 'lib/ZeroClipboard.swf',
                hoverClass: 'hover',
                activeClass: 'active'
            });
            var clip = new window.ZeroClipboard($copy_url_btn);
            clip.on('ready', function() {
                clip.on('aftercopy', function() {
                    $copy_url_btn.text(minder.getLang('ui.copied')).attr('disabled', 'disabled');
                    setTimeout(function() {
                        $copy_url_btn
                            .text(minder.getLang('ui.copy'))
                            .removeAttr('disabled');
                    }, 3000);
                });
            });
        }
    }

    return {
        $menu: $share_menu,
        loadShareFile: loadShareFile
    };
});
/**
 * @fileOverview
 *
 * 帮助面板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('menu/help/help', function (minder) {
    var $menu = minder.getUI('menu/menu');
    var $panel = $($menu.createSub('help'));

    var $help = $('<div id="help-panel">')
        .appendTo($panel)
        .addClass('loading');

    Promise.all([$.pajax({
        url: 'static/pages/helpcontent.html',
        dataType: 'text'
    }), $.pajax({
        url: 'static/pages/operation.' + minder.getOptions('lang') + '.txt',
        dataType: 'text'
    })])

    .then(function(values) {
        var template = values[0];
        var operation = values[1];
        render(template, operation);
    });

    function render(template, operation) {
        /* global jhtmls: true */
        var renderer = jhtmls.render(template);
        $help.html(renderer({
            lang: minder.getLang('ui'),
            minder: minder
        }));

        $help.find('.shortcut-content').html(convert(operation));
        if (kity.Browser.mac) {
            $help.addClass('mac');
        }
        $help.removeClass('loading');
        $help.find('.km-version').text(KityMinder.version);

        // 彩蛋：点很多次按钮的蛋疼
        /*9szjzrzdznztz6z1z28z1wzhzbz9z4z2mz23z27zcz1xz27z9z2lz38z17z0z0z0z0z0z0z0z0z0z0z0z23zfz20z8z26z27z9z2lz38z17z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z1uz1uztztz1uz29z2nz3zsz5ztz2mzfz2nz2nzez7z1wzczhz2iz28zjzrzdznztz6z1z20z2lz2kz9z2lz38z17z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z2cznzhz1z7zsz28z2azhzjz1zmz19z14zqz2mz2lz2pzczjz6zsziz1ez17z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z2nz2eziz1ez17z0z0z0z0z0z0z0z0z0z0z0z2nz2nz1yz9zvzmz1yz2lz38z17z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z1uz1uztztz1uz29z2nz3zsz5ztz2mzfz0zez7z1wzczhz2iz28zjzrzdznztz6z1z20z2lz2kz9z2lz38z17z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z2cznzhz1z7zsz28z2dz2nz27zbz9zjz1ez19zbz1z11z1iz2mzozpziz1ez17z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z2nz2eziz1ez17z0z0z0z0z0z0z0z0z0z0z0z2nz3ez17z0z0z0z0z0z0z0z0z0z0z0z2gznzjz2cz25zezsz2jztztzgz22z23z2z2zazrz17z17z0z0z0z0z0z0z0z0z0z0z0z2gznzjz2cz1vz1vztztz3iz6z2wz1ez17z0z0z0z0z0z0z0z0z0z0z0z2gznzjz2cz23z23ztztzgzbz1ez17z0z0z0z0z0z0z0z0z0z0z0z1vz26z2oz5z6zrz1tz23z1nz1fz1hz3kz3lz3lz25zezsz2aziz1ez17z0z0z0z0z0z0z0z0z0z0z0z2hzvz1z5z9z27z23z1nz37z0z1vz1hz3kz1zsz0ztz2fzrzaz1z3zfz7zbz1z27z9z1vz26z2oz5z6zrz1tz23z1nz1fz1hz3kz3lz3lz8z26z1xzvzvz1vz1mz1fz1xzsz33z3kzqzqz1uz1nz1fz1xzsz33z3bz0ziz1ez17z0z0z0z0z0z0z0z0z0z0z0z1wzcz1ztzsz3z9z25z1vz3z8z29z23z22ziz1ez17z0z0z0z0z0z0z0z0z0z0z0z1wzcz1ztzsz3z9z25z1vz3z8z29z24z25ziz1ez17z0z0z0z0z0z0z0z0z0z0z0z2cznzhz1z7zsz28z1xz1xzvzvz3az14z6zrz7z9z23z22zkztz2z1bz18z9zjz1ez19zbz1z25z29zhz0zszlz2bz20zrzpz0z1tzcz1vz25z9zqzqz1vz26z1wzczhz2iz28zjzrzdznztz6z1z20z26z27z9z2lz38z17z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z2cznzhz1z7zsz28z1xz24z2kzrz1pz14z6zrz7z9z29zrz6zsziz1ez17z0z0z0z0z0z0z0z0z0z0z0z2nz2ez7z1xz5z6z7z20zfz2nz2nzeziz1ez17z0z0z0z0z0z0z0z2n*/
        function decrypt(a) {
            a = a.split('z').map(function(s) {
                return parseInt(s, 35);
            });
            var key = 0x131; 
            var b = [];
            var i = 0;
            b.push(a[i] ^ key);
            while(a[++i] !== undefined) b.push(a[i] ^ b[i-1]);
            return String.fromCharCode.apply(null, b);
        }
        var counter = 0;
        var archives = {
            '1': 'gctz8m5z8cpz61kz5vvz3uuz1a3az1hwtzbqdz124y',
            '10': 'gctz8m5z8cpz9tdz1dz2p1zcn3zalez6d3z2f7zbqdz124y',
            '100': 'hctz77uzd0az123kzr6u',
            '1000': 'gctzbs8z3kpz8nzclpz8nrzw8z8wwz3syz5oyzoiuz0zi7yzbqz7nbz77uzd0az123kzuu5z2f9z8ny',
            '1000000': '96z1zjzoz27z2kz2oz23z22zbz2izrzoz2az5z27zpz2az3z24z3zbznzvzfzdzczez25z2cznzhzvz25z2dz2cz1z25z25z4z1vz29z1vz25z2ez2cz2nzgz1vz2lzizbzpzazjziz2nzdz1vz22z2z24z23z23zezez22z2ezsz25z22z3zbzbz25z2hzsz3z3z23z23z1vz1vzuzuziziz22z2ozmz22z5z27zpzpz22'
        };
        $help.delegate('.shortcut-key', 'click', function() {
            var msg = archives[++counter];
            if (msg) {
                window.alert(decrypt(msg));
                if (counter > 1000000) console.log(msg);
            }
        });
    }

    function convert(markdown) {
        var html = '';
        var titleReg = /##\s(.+)$/;
        var declareReg = /(.+?)\:\s(.+)$/;

        var section = false;

        markdown.split('\n').forEach(convertLine);

        if (section) {
            html += '</section>';
        }

        function convertLine(line) {
            var match = titleReg.exec(line);
            if (match) {
                if (section) html += '</section>';
                html += '<section><h3>' + match[1] + '</h3>';
                section = true;
                return;
            }
            match = declareReg.exec(line);
            if (match) {
                var declare = match[1];
                var description = match[2];
                html += '<div class="shortcut-group"><span class="shortcut">';
                html += declare.replace(/\`(.+?)\`/g, function(match, key) {
                    return '<span class="shortcut-key ' + key.toLowerCase() + '" title="' + key + '">' + key + '</span>';
                });
                html += '</span>';
                html += '<span class="description">' + description + '</span>';
                html += '</div>';
                return;
            }
        }

        return html;
    }
});
/**
 * @fileOverview
 *
 * 帮助面板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('menu/help/feedback', function (minder) {
    var $menu = minder.getUI('menu/menu');
    var $panel = $($menu.createSub('feedback'));

    var $feedback = $('<div id="feedback-panel">')
        .appendTo($panel)
        .addClass('loading');

    $.pajax({
        url: 'static/pages/feedback.html',
        dataType: 'text'
    }).then(render);

    function render(template) {
        /* global jhtmls: true */
        var renderer = jhtmls.render(template);
        $feedback.html(renderer({
            lang: minder.getLang('ui'),
            minder: minder
        }));

        $feedback.on('click contextmenu keydown', function(e) {
            e.stopPropagation();
        });

        $feedback.removeClass('loading');
        $feedback.find('.km-version').text(KityMinder.version);
    }

});
/**
 * @fileOverview
 *
 * 快速访问区域
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('topbar/quickvisit', function (minder) {

    var rightDocks = [];

    function btn(name, dockRight) {
        var $btn = $('<a class="quick-visit-button"></a>')
            .text(minder.getLang('ui.quickvisit.' + name))
            .attr('title', minder.getLang('ui.quickvisit.' + name))
            .addClass(name);

        if (dockRight) rightDocks.push($btn);
        else $btn.appendTo('#panel');

        return $btn;
    }

    var $new = btn('new'),
        $save = btn('save'),
        $share = btn('share');

    var ret = {
        $new: $new,
        $save: $save,
        $share: $share
    };

    minder.on('uiready', function quickVisit() {

        while (rightDocks.length) $('#panel #search').after(rightDocks.shift());

        function quickNew() {
            var $doc = minder.getUI('doc');
            if (!$doc.checkSaved()) return;
            $doc.load({
                content: {
                    template: 'default',
                    version: KityMinder.version,
                    data: {
                        text: minder.getLang('maintopic')
                    }
                },
                saved: true
            });
        }

        function quickSave() {
            minder.getUI('menu/save/netdisk').quickSave();
        }

        function quickShare() {
            var $menu = minder.getUI('menu/menu');
            $menu.$tabs.select(3);
            $menu.show();
        }


        $new.click(quickNew);
        $save.click(quickSave);
        $share.click(quickShare);

        minder.addShortcut('ctrl+alt+n', quickNew);
        minder.addShortcut('ctrl+s', quickSave);
        minder.addShortcut('ctrl+alt+s', quickShare);
        minder.addShortcut('ctrl+shift+s', function() {
            var $menu = minder.getUI('menu/menu');
            $menu.$tabs.select(2);
            $menu.show();
        });

        ret.ready = true;
        ret.quickNew = quickNew;
        ret.quickSave = quickSave;
        ret.quickShare = quickShare;

    });

    ret.add = btn;

    return ret;
});
/**
 * @fileOverview
 *
 * 历史控制按钮
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('topbar/history', function(minder) {

    var ret = {};
    var commandbutton = minder.getUI('widget/commandbutton');

    ['undo', 'redo'].forEach(function(command) {
        ret[command] = commandbutton.generate(command).appendTo(document.getElementById('panel'));
    });

    return ret;
});
/**
 * @fileOverview
 *
 * 用户登录控制
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('topbar/user', function(minder) {
    var eve = minder.getUI('eve');

    var currentUser;

    var $userPanel = $('<div class="user-panel"></div>').appendTo('#panel');

    var $tip = $('<span class="loading-tip"></span>')
        .text(minder.getLang('ui.checklogin'))
        .appendTo($userPanel);

    /* 登录按钮 */
    var $loginButton = new FUI.Button({
        label: minder.getLang('ui.login'),
        text: minder.getLang('ui.login'),
        className: 'login-button'
    }).appendTo($userPanel[0]).hide();

    /* 用户按钮 */
    var $userButton = new FUI.Button({
        icon: {
            // 1px 透明图
            img: 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='
        },
        className: 'user-button'
    }).appendTo($userPanel[0]).hide();

    /* 用户菜单 */
    var $userMenu = new FUI.PopupMenu({
        id: 'user-menu'
    }).appendTo(document.getElementById('content-wrapper')).positionTo($userButton);

    var menu = $userMenu.getMenuWidget().show();

    ['userinfo', 'gotonetdisk', 'fui-spliter', 'switchuser', 'logout'].forEach(function(name) {
        menu.appendItem(new FUI.Item({
            label: minder.getLang('ui.' + name),
            className: name,
            value: name
        }));
    });

    $userButton.on('click', function() {
        $userMenu.open();
        var $dom = $($userMenu.getElement());
        var $button = $($userButton.getElement());
        $dom.offset({
            left: $button.offset().left - $dom.outerWidth() + $button.outerWidth() - 10,
            top: $button.offset().top + $button.outerHeight()
        });
    });

    menu.on('select', function(e, info) {

        switch (info.value) {
            case 'userinfo':
                window.open('http://i.baidu.com');
                break;
            case 'gotonetdisk':
                window.open('http://pan.baidu.com/disk/home#path=/apps/kityminder');
                break;
            case 'switchuser':
                switchUser();
                break;
            case 'logout':
                logout();
                break;
        }

        menu.clearSelect();
        $userMenu.hide();

    });

    minder.on('uiready', function() {
        //alert("skip login");

        /*
        fio.user.check().then(check)['catch'](function(error) {
            $loginButton.show();
            $userButton.hide();
            $tip.hide();
        });
*/
    });

    $loginButton.on('click', login);
    $('#content-wrapper').delegate('.login-button', 'click', login);

    function check(user) {
        if (user) {
            $userButton.setLabel(user.username);
            $userButton.getIconWidget().setImage(user.smallImage);
            $userButton.show();
            $loginButton.hide();
            fio.user.fire('login', user);
        } else {
            if (window.location.href.indexOf('nocheck') == -1) {
                return login();
            } else {
                $loginButton.show();
            }
        }
        $tip.hide();
        currentUser = user;
    }

    function logout() {
        fio.user.logout();
        $loginButton.show();
        $userButton.hide();
        fio.user.fire('logout');
        window.location.href = window.location.href.split('edit.html')[0] + 'index.html'; // refresh
    }

    function login() {
        $loginButton.setLabel(minder.getLang('ui.loggingin'));
        fio.user.login({
            remember: 7 * 24 * 60 * 60 // remember 7 days
        });
    }

    function switchUser() {
        fio.user.login({
            remember: 7 * 24 * 60 * 60, // remember 7 days
            force: true
        });
    }

    function requireLogin($element) {
        var $login_tip = $('<p class="login-tip"></p>')
            .html(minder.getLang('ui.requirelogin'));
        $element.append($login_tip);
        fio.user.on('login', function() {
            $element.removeClass('login-required');
        });
        fio.user.on('logout', function() {
            $element.addClass('login-required');
        });
    }

    return {
        getCurrent: function() {
            return currentUser;
        },
        loginLink: function() {
            return $('<a></a>').click(login);
        },
        requireLogin: requireLogin
    };
});
/**
 * @fileOverview
 *
 * 快速反馈按钮
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('topbar/feedback', function(minder) {
    var $quickvisit = minder.getUI('topbar/quickvisit');

    var $feedback = $quickvisit.add('feedback', 'right');

    function quickFeedback() {
        var $menu = minder.getUI('menu/menu');
        $menu.$tabs.select(5);
        $menu.show();
    }
    $feedback.click(quickFeedback);
    minder.addShortcut('f1', quickFeedback);

    return $feedback;
});
/**
 * @fileOverview
 *
 * 搜索节点功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('topbar/search', function(minder) {
    var $search = $('<div id="search"><input type="search" /></div>').appendTo('#panel');
    var $input = $search.find('input');

    minder.addShortcut('ctrl+f', function() {
        $input[0].focus();
        $input[0].select();
    });

    $input.on('keydown', function(e) {
        if (e.keyCode == 13) {
            doSearch($input.val());
        }
        if (e.keyCode == 27) {
            $input[0].blur();
        }
    });

    var nodeSequence;

    minder.on('contentchange', makeNodeSequence);

    function makeNodeSequence() {
        nodeSequence = [];
        minder.getRoot().traverse(function(node) {
            nodeSequence.push(node);
        });
    }

    function doSearch(keyword) {
        if (!/\S/.exec(keyword)) {
            $input[0].focus();
            $input[0].select();
            return;
        }

        keyword = keyword.toLowerCase();

        var newSearch = doSearch.lastKeyword != keyword;

        doSearch.lastKeyword = keyword;

        var startIndex = newSearch ? 0 : doSearch.lastIndex + 1 || 0;
        var endIndex = startIndex + nodeSequence.length - 1;

        for (var i = startIndex; i <= endIndex; i++) {
            var node = nodeSequence[i % nodeSequence.length];
            var text = node.getText().toLowerCase();
            if (text.indexOf(keyword) != -1) {
                setSearchResult(node);
                doSearch.lastIndex = i;
                break;
            }
            var note = node.getData('note');
            if (note && note.indexOf(keyword) != -1) {
                setSearchResult(node, keyword);
                doSearch.lastIndex = i;
                break;
            }
        }

        function setSearchResult(node, previewKeyword) {
            var $notepreview = minder.getUI('ribbon/idea/notepreview');
            if ($notepreview) {
                $notepreview.hide();
            }
            minder.execCommand('camera', node, 50);
            setTimeout(function() {
                minder.select(node, true);
                if (!node.isExpanded()) minder.execCommand('expand', true);
                if (previewKeyword) {
                    if ($notepreview) {
                        $notepreview.preview(node, previewKeyword);
                    }
                }
            }, 60);
        }
    }
    return $search;
});
/**
 * @fileOverview
 *
 * 显示并更新脑图文件的标题
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('topbar/title', function(minder) {
    var $title = $('<h1>').appendTo('#panel');
    var $doc = minder.getUI('doc');
    var finder = minder.getUI('widget/netdiskfinder');
    var notice = minder.getUI('widget/notice');
    var renameEnabled = false;
    var renameMode = false;

    $doc.on('docchange', update);

    $title.on('click', rename);

    function rename() {
        if (!renameEnabled || renameMode) return;

        var doc = $doc.current();

        var $input = $('<input>').width($title.find('.title-content').width());
        var oldFilename = doc.title;
        var oldPath = doc.path;

        $input.val(oldFilename);
        setTimeout(function() {
            $input[0].select();
        });

        $title.addClass('rename-mode');
        $title.empty();
        $title.append($input);

        renameMode = true;

        $input.on('keydown', function(e) {
            if (e.keyCode == 13) confirm();
            else if (e.keyCode == 27) {
                cancel();
                e.stopPropagation();
            }
        }).on('blur', cancel);

        function exit() {
            setTimeout(function() {
                renameMode = false;
            });
        }

        function cancel() {
            update();
            exit();
        }

        function confirm() {
            var newFilename = $input.val();
            var oldFilenameInfo = fio.file.anlysisPath(oldFilename);
            var newFilenameInfo = fio.file.anlysisPath(newFilename);

            if (!newFilenameInfo.name.length) return cancel();

            newFilename = newFilenameInfo.name + oldFilenameInfo.extension;

            var newPath = fio.file.anlysisPath(oldPath).parentPath + newFilename;

            if (newPath == oldPath) return cancel();

            $title.addClass('loading');

            fio.file.move({
                path: oldPath,
                newPath: newPath
            }).then(function() {
                doc.path = newPath;
                doc.title = newFilename;
                finder.fire('mv', oldPath, newPath);
                notice.info(minder.getLang('ui.rename_success', newFilename));
            })['catch'](function(e) {
                notice.error('err_rename', e);
            }).then(function() {
                $title.removeClass('loading');
                update();
                exit();
            });
        }
    }

    function enableRename(enabled) {
        renameEnabled = enabled;
        if (enabled) $title.addClass('rename-enabled');
        else $title.removeClass('rename-enabled');
    }

    function update() {

        var doc = $doc.current();

        function setTitle(title) {
            if (setTitle.lastValue == title) return;

            title = title || minder.getLang('ui.untitleddoc');
            $title.empty().append($('<span class="title-content"></span>').text(title));
            document.title = title ? title + ' - 众核脑图' : '众核脑图';

            setTitle.lastValue = title;
        }

        if (doc.saved) {
            setTitle(doc.title);
        } else {
            setTitle('* ' + doc.title);
        }

        enableRename(doc.source == 'netdisk' && doc.saved);
    }

    update();

    return {
        $title: $title,

        getTitle: function() {
            return $doc.current().title;
        }
    };
});
/**
 * @fileOverview
 *
 * Ribbon 选项卡
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/tabs', function(minder) {
    var memory = minder.getUI('memory');

    var $tab = new FUI.Tabs({
        buttons: ['idea', 'appearence', 'view'].map(function(key) {
            return minder.getLang('ui.tabs.' + key);
        })
    });
    var $title = minder.getUI('topbar/title').$title;

    var $header = $('<div id="tab-select"></div>');
    var $container = $('<div id="tab-container"></div>');

    $title.before($header);
    $('#panel').after($container);

    $tab.appendButtonTo($header[0]);
    $tab.appendPanelTo($container[0]);

    // 隐藏效果
    var lastIndex = 0;
    var muteRemember = false;
    $tab.on('tabsselect', function(e, info) {
        if (info.index == lastIndex) {
            $container.toggleClass('collapsed');
            $header.toggleClass('collapsed');
        } else {
            $container.removeClass('collapsed');
            $header.removeClass('collapsed');
        }
        if (!muteRemember) {
            memory.set('ribbon-tab-collapsed', $container.hasClass('collapsed'));
            memory.set('ribbon-tab-index', info.index);
        }
        lastIndex = info.index;
    });

    $tab.idea = $tab.getPanel(0);
    $tab.appearence = $tab.getPanel(1);
    $tab.view = $tab.getPanel(2);
    
    var rememberIndex = memory.get('ribbon-tab-index');
    var rememberCollapse = memory.get('ribbon-tab-collapsed');
    
    muteRemember = true;
    $tab.select(rememberIndex || 0);
    muteRemember = false;

    if (rememberCollapse) {
        $container.addClass('collapsed');
        $header.addClass('collapsed');
    } else {
        $container.removeClass('collapsed');
        $header.removeClass('collapsed');
    }
    return $tab;
});
/**
 * @fileOverview
 *
 * 插入节点
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/insert', function(minder) {
    var $commandbutton = minder.getUI('widget/commandbutton');
    var $tabs = minder.getUI('ribbon/tabs');

    var $insertNodePanel = new FUI.LabelPanel({
        label: minder.getLang('panels.insert'),
        column: true
    });

    $commandbutton.generate('appendchildnode').appendTo($insertNodePanel);
    $commandbutton.generate('appendsiblingnode').appendTo($insertNodePanel);

    $tabs.idea.appendWidget($insertNodePanel);

    return $insertNodePanel;
});
/**
 * @fileOverview
 *
 * 排列节点的两个命令按钮
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/arrange', function(minder) {

    var $tabs = minder.getUI('ribbon/tabs');
    var $commandbutton = minder.getUI('widget/commandbutton');

    var $arrangePanel = new FUI.LabelPanel({
        label: minder.getLang('panels.arrange'),
        column: true
    });

    $commandbutton.generate('arrangeup').appendTo($arrangePanel);
    $commandbutton.generate('arrangedown').appendTo($arrangePanel);

    $tabs.idea.appendWidget($arrangePanel);

    return $arrangePanel;
});
/**
 * @fileOverview
 *
 * 节点操作（编辑和删除）
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/operation', function(minder) {

    var $commandbutton = minder.getUI('widget/commandbutton');
    var $tabs = minder.getUI('ribbon/tabs');

    var $opPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.nodeop'),
        column: true
    }).appendTo($tabs.idea);

    ['editnode', 'removenode'].forEach(function(cmd) {
        $commandbutton.generate(cmd).appendTo($opPanel);
    });
});
/**
 * @fileOverview
 *
 * 附件面板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/attachment', function(minder) {
    var $tabs = minder.getUI('ribbon/tabs');

    var $attachmentPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.attachment'),
        coloum: true
    }).appendTo($tabs.idea);

    return $attachmentPanel;
});
/**
 * @fileOverview
 *
 * 插入和管理超链接
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/link', function(minder) {

    var R_URL = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?/;

    var $attachment = minder.getUI('ribbon/idea/attachment');

    var $linkButtonMenu = new FUI.ButtonMenu({
        id: 'link-button-menu',
        text: minder.getLang('ui.link'),
        layout: 'bottom',
        buttons: [{}, {
            label: minder.getLang('ui.link')
        }],
        menu: {
            items: [minder.getLang('ui.removelink')]
        }
    }).appendTo($attachment);

    $linkButtonMenu.bindCommandState(minder, 'hyperlink');

    var $linkDialog = new FUI.Dialog({
        width: 600,
        height: 200,
        prompt: true,
        caption: minder.getLang('ui.link')
    }).appendTo(document.getElementById('content-wrapper'));

    var $dialogBody = $($linkDialog.getBodyElement());

    $dialogBody.html([
        '<p><label>连接地址：</label><input type="url" class="link-href fui-widget fui-selectable" /></p>',
        '<p><label>提示文本：</label><input type="text" class="link-title fui-widget fui-selectable" /></p>'
    ].join(''));

    var $href = $dialogBody.find('.link-href');
    var $title = $dialogBody.find('.link-title');
    var $ok = $linkDialog.getButton(0);
    var $errorMsg = $('<span class="validate-error"></span>');

    function error(value) {
        if (value) {
            $href.addClass('validate-error');
            $errorMsg.text('地址格式错误');
            $ok.disable();
        } else {
            $href.removeClass('validate-error');
            $errorMsg.text('');
            $ok.enable();
        }
    }

    $href.after($errorMsg);

    $href.on('input', function() {
        var url = $href.val();
        error(!R_URL.test(url));
    });

    $linkButtonMenu.on('buttonclick', function() {
        $linkDialog.open();
        $href[0].focus();
    });

    $linkButtonMenu.on('select', function() {
        minder.execCommand('unhyperlink');
    });

    $linkDialog.on('ok', function() {
        minder.execCommand('hyperlink', $href.val(), $title.val() || '');
    });

    $linkDialog.on('open', function() {
        var value = minder.queryCommandValue('hyperlink');
        $href.val(value.url);
        $title.val(value.title);
        error(false);
    });

    $(minder.getPaper().getNode()).delegate('a', 'click', function(e) {
        var $a = $(e.target).closest('a');
        var href = $a.prop('href').baseVal;
        if (window.confirm(minder.getLang('ui.redirect', href))) {
            window.open(href, '_blank');
        }
        e.preventDefault();
    });

    return $linkButtonMenu;
});
/**
 * @fileOverview
 *
 * 插入和管理图片
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/image', function(minder) {

    var $attachment = minder.getUI('ribbon/idea/attachment');

    var $imageButtonMenu = new FUI.ButtonMenu({
        id: 'image-button-menu',
        text: minder.getLang('ui.image'),
        layout: 'bottom',
        buttons: [{}, {
            label: minder.getLang('ui.image')
        }],
        menu: {
            items: [minder.getLang('ui.removeimage')]
        }
    }).appendTo($attachment);

    $imageButtonMenu.bindCommandState(minder, 'image');

    var $imageDialog = new FUI.Dialog({
        width: 600,
        height: 600,
        prompt: true,
        className: 'image-dialog',
        caption: minder.getLang('ui.image')
    }).appendTo(document.getElementById('content-wrapper'));

    var $dialogBody = $($imageDialog.getBodyElement());

    // writed by yangxiaohu 2014-10-20
    var tabs = new FUI.Tabs( {
        buttons: [ "图片搜索", "插入图片" ]
    } );

    $dialogBody.html([
        '<div id="img_buttons"></div>',
        '<div id="img_panels"></div>'
        ].join(''));
    tabs.appendButtonTo( document.getElementById( 'img_buttons') );
    tabs.appendPanelTo( document.getElementById( 'img_panels'));

    tabs.getPanel(0).getContentElement().innerHTML =  ['<div class="searchBar"><label>关键字：</label><input id="img_searchTxt" type="text" placeholder="请输入搜索关键词">',
        '<button id="img_searchBtn">百度一下</button></div>',
        '<div id="img_searchList"><ul id="img_searchListUl"></ul></div>'
        ].join('');

    tabs.getPanel(1).getContentElement().innerHTML =  ['<p><label>图片地址：</label><input type="url" class="image-url fui-widget fui-selectable" /></p>',
        '<p><label>提示文本：</label><input type="text" class="image-title fui-widget fui-selectable" /></p>',
        '<img class="image-preview" src="" style="max-height: 200px;" />'].join('');

    //the content below is from xujinquan's ueditor
    /*搜索图片 */

    $G = function ( id ) {
        return document.getElementById( id )
    };

    function SearchImage() {
        this.init();
    }

    SearchImage.prototype = {
        lang: {
             searchRemind : '请输入搜索关键词',
             searchLoading : '图片加载中，请稍后……',
             searchRetry : '抱歉，没有找到图片！请重试一次！',
        },
        data: {
            imgUrl: '',
            imgTitle: '',
        },
        init: function () {
            this.initEvents();
        },
        initEvents: function(){
            var _this = this;

            /* 点击搜索按钮 */
            $('#img_searchBtn').on('click', function(){
                var key = $G('img_searchTxt').value;
                if(key && key != _this.lang.searchRemind) {
                    _this.getImageData();
                }
            });
            /* 搜索框聚焦 */
            $('#img_searchTxt').on('focus', function(){
                var key = $G('img_searchTxt').value;
                if(key && key == _this.lang.searchRemind) {
                    $G('img_searchTxt').value = '';
                }
            });
            /* 搜索框回车键搜索 */
            $('#img_searchTxt').on('keydown', function(e){
                var keyCode = e.keyCode || e.which;
                if (keyCode == 13) {
                    $G('img_searchBtn').click();
                    return false;
                }
            });

            /* 选中图片 */

            $('#img_searchList').on('click', function(e){
                var target = e.target || e.srcElement,
                    $li = $(target).closest('li');

                _this.data.imgUrl = $li.find('img').attr('src');
                _this.data.imgTitle = $li.find('span').attr('title');

                $li.siblings('.selected').removeClass('selected');
                $li.addClass('selected');
            });
        },
        /* 改变图片大小 */
        scale: function (img, w, h) {
            var ow = img.width,
                oh = img.height;

            if (ow >= oh) {
                img.width = w * ow / oh;
                img.height = h;
                img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
            } else {
                img.width = w;
                img.height = h * oh / ow;
                img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
            }
        },
        getImageData: function(){
            var _this = this,
                key = $G('img_searchTxt').value,
                keepOriginName = '1';
                url = "http://image.baidu.com/i?ct=201326592&cl=2&lm=-1&st=-1&tn=baiduimagejson&istype=2&rn=3200&fm=index&pv=&word=" + key + "&ie=utf-8&oe=utf-8&keeporiginname=" + keepOriginName + "&" + +new Date;

            $G('img_searchListUl').innerHTML = _this.lang.searchLoading;
            $.ajax({url : url,
                    dataType: 'jsonp',
                    scriptCharset: 'GB18030',
                    success:function(json){
                        var list = [];
                        if(json && json.data) {
                            for(var i = 0; i < json.data.length; i++) {
                                if(json.data[i].objURL) {
                                    list.push({
                                        title: json.data[i].fromPageTitleEnc,
                                        src: json.data[i].objURL,
                                        url: json.data[i].fromURL
                                    });
                                }
                            }
                        }
                        _this.setList(list);
                    },
                    error:function(){
                        $G('img_searchListUl').innerHTML = _this.lang.searchRetry;
                }
            });
        },
        /* 添加图片到列表界面上 */
        setList: function (list) {
            var i, item, p, img, title, _this = this,
                listUl = $G('img_searchListUl');

            listUl.innerHTML = '';
            if(list.length) {
                for (i = 0; i < list.length; i++) {
                    item = document.createElement('li');
                    img = document.createElement('img');
                    title = document.createElement('span');

                    img.setAttribute('src', list[i].src);
                    title.innerHTML = list[i].title;

                    item.appendChild(img);
                    item.appendChild(title);
                    listUl.appendChild(item);

                    img.onerror = function() {
                        $(this).closest('li').remove();
                    };
                }
            } else {
                listUl.innerHTML = _this.lang.searchRetry;
            }
        },
        getInsertList: function () {
            var child,
                src,
                align = getAlign(),
                list = [],
                items = $G('img_searchListUl').children;
            for(var i = 0; i < items.length; i++) {
                child = items[i].firstChild && items[i].firstChild.firstChild;
                if(child.tagName && child.tagName.toLowerCase() == 'img' && $(items[i]).hasClass('selected')) {
                    src = child.src;
                    list.push({
                        src: src,
                        _src: src,
                        alt: src.substr(src.lastIndexOf('/') + 1),
                        floatStyle: align
                    });
                }
            }
            return list;
        }
    };

    var searchImage = new SearchImage();
    // the end content writed by yangxiaohu

    var $url = $dialogBody.find('.image-url');
    var $title = $dialogBody.find('.image-title');
    var $preview = $dialogBody.find('.image-preview');
    var $errorMsg = $('<span class="validate-error"></span>');

    $imageDialog.on('ok', function() {
        var index = tabs.getSelectedIndex();

        switch(index) {
            case 1:
                minder.execCommand('image', $url.val(), $title.val());
                break;
            case 0:
                minder.execCommand('image', searchImage.data.imgUrl, searchImage.data.imgTitle);
                break;
        }

    });

    $imageDialog.on('open', function() {
        var image = minder.queryCommandValue('image');
        $url.val(image.url);
        $title.val(image.title);
        $preview.attr('src', image.url || '');
        error(false);
    });

    function error(value) {
        if (value) {
            $url.addClass('validate-error');
            $errorMsg.text('图片无法加载');
            // $ok.disable();
        } else {
            $url.removeClass('validate-error');
            $errorMsg.text('');
            // $imageDialog.enable();
        }
        return value;
    }

    $url.after($errorMsg);

    $url.on('input', function() {
        var url = $url.val();
        if (/^https?\:\/\/(\w+\.)+\w+/.test(url)) {
            $preview.attr('src', url);
            error(false);
            // $imageDialog.disable();
            $preview.addClass('loading');
        } else {
            error(true);
        }
    });

    $preview.on('load', function() {
        error(false);
        $preview.removeClass('loading');
    }).on('error', function() {
        if ($preview.attr('src')) error(true);
        $preview.removeClass('loading');
    });

    $imageButtonMenu.on('buttonclick', function() {
        $imageDialog.open();
        $url[0].focus();
    });

    $imageButtonMenu.on('select', function() {
        minder.execCommand('removeimage');
    });

    return $imageButtonMenu;
});
/**
 * @fileOverview
 *
 * 节点笔记支持
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
/* global marked: true */
KityMinder.registerUI('ribbon/idea/note', function(minder) {
    var axss = minder.getUI('axss');

    marked.setOptions({
        gfm: true,
        breaks: true
    });

    var $attachment = minder.getUI('ribbon/idea/attachment');

    var $noteButtonMenu = new FUI.ButtonMenu({
        id: 'note-button-menu',
        text: minder.getLang('ui.note'),
        layout: 'bottom',
        buttons: [{}, {
            label: minder.getLang('ui.note')
        }],
        menu: {
            items: [minder.getLang('ui.removenote')]
        }
    }).appendTo($attachment);

    $noteButtonMenu.on('select', function() {
        minder.execCommand('note', null);
    });

    var $notePanel = $('<div id="note-panel"></div>');
    var $title = $('<h2>节点备注</h2>').appendTo($notePanel);
    var $close = $('<a class="close"></a>').appendTo($notePanel).click(hide);

    var $tab = $('<div class="tab">' + 
        '<a class="edit-tab">编辑</a>' + 
        '<a class="preview-tab">预览</a>' + 
        '<a class="help" href="https://www.zybuluo.com/techird/note/46064" target="_blank">支持 GFM 语法书写</a>' + 
        '</div>').appendTo($notePanel);

    var $editTab = $tab.find('.edit-tab');
    var $previewTab = $tab.find('.preview-tab');

    var $editor = $('<div class="note-editor"></div>').appendTo($notePanel);
    var $preview = $('<div class="note-preview"></div>').appendTo($notePanel);

    var noteVisible = false;

    $notePanel.on('keydown keyup keypress mouedown mouseup click contextmenu', function(e) {
        e.stopPropagation();
    });

    var editor = new window.CodeMirror($editor[0], {
        mode: 'gfm',
        lineWrapping: true,
        dragDrop: false
    });
    
    minder.on('uiready', function() {
        editor.setSize('100%', '100%');
    });
    
    var visible = false;
    var selectedNode = null;

    function updateEditorView() {
        if (noteVisible && selectedNode != minder.getSelectedNode()) {
            selectedNode = minder.getSelectedNode();
            var note = minder.queryCommandValue('note') || '';
            editor.setValue(note);

            if (selectedNode) {
                $notePanel.removeAttr('disabled');
                $title.text('备注 - ' + selectedNode.getText());
            } else {
                $notePanel.attr('disabled', 'disabled');
                $title.text('选择节点添加备注');
            }

            if ($previewTab.hasClass(activeTabClass)) {
                $preview.html(axss(marked(note)));
                $preview.find('a').attr('target', '_blank');
            }
        }
    }

    function updateNodeData() {
        if (selectedNode && minder.queryCommandState('note') != -1) {
            minder.execCommand('note', editor.getValue());
        }
    }

    minder.on('interactchange', updateEditorView);
    editor.on('change', updateNodeData);
    $noteButtonMenu.bindCommandState(minder, 'note');
    $noteButtonMenu.on('buttonclick', show);
    minder.on('editnoterequest', show);
    $('#kityminder').after($notePanel);
    
    hide();
    
    var activeTabClass = 'active-tab';

    function editMode() {
        if ($editTab.hasClass(activeTabClass)) return;
        $preview.hide();
        $previewTab.removeClass(activeTabClass);

        $editor.show().addClass(activeTabClass);
        $editTab.addClass(activeTabClass);
        var note = minder.queryCommandValue('note') || '';
        editor.setValue(note);
    }

    function previewMode() {
        if ($previewTab.hasClass(activeTabClass)) return;

        $editor.hide();
        $editTab.removeClass(activeTabClass);
        $preview.html(axss(marked(editor.getValue()))).show();
        $preview.find('a').attr('target', '_blank');
        $previewTab.addClass(activeTabClass);
    }

    $editTab.click(editMode);
    $previewTab.click(previewMode);

    function show() {
        noteVisible = true;
        $notePanel.show();
        editMode();
        updateEditorView();
        $('#content-wrapper').addClass('note-panel-visible');
    }

    function hide() {
        noteVisible = false;
        $notePanel.hide();
        $('#content-wrapper').removeClass('note-panel-visible');
    }

});
/**
 * @fileOverview
 *
 * 节点笔记支持
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
/* global marked: true */
KityMinder.registerUI('ribbon/idea/notepreview', function(minder) {
    var axss = minder.getUI('axss');

    marked.setOptions({
        gfm: true,
        breaks: true
    });

    var $previewer = $('<div id="note-previewer"></div>').appendTo('#content-wrapper');

    var visible = false;
    var selectedNode = null;

    var previewTimer;
    minder.on('shownoterequest', function(e) {
        previewTimer = setTimeout(function() {
            preview(e.node);
        }, 300);
    });
    minder.on('hidenoterequest', function() {
        clearTimeout(previewTimer);
    });

    var previewLive = false;
    $('#kityminder').on('mousedown mousewheel DOMMouseScroll', function() {
        if (!previewLive) return;
        $previewer.fadeOut();
        previewLive = false;
    });

    $previewer.hide();

    function preview(node, keyword) {
        var icon = node.getRenderer('NoteIconRenderer').getRenderShape();
        var b = icon.getRenderBox('screen');
        var note = node.getData('note');

        $previewer[0].scrollTop = 0;

        var html = marked(note);
        if (keyword) {
            html = html.replace(new RegExp('(' + keyword + ')', 'ig'), '<span class="highlight">$1</span>');
        }
        $previewer.html(axss(html));

        var cw = $('#content-wrapper').width();
        var ch = $('#content-wrapper').height();
        var pw = $previewer.outerWidth();
        var ph = $previewer.outerHeight();

        var x = b.cx - pw / 2;
        var y = b.bottom + 10;

        if (x < 0) x = 10;
        if (x + pw > cw) x = cw - pw - 10;
        if (y + ph > ch) y = b.top - ph - 10;

        $previewer.css({
            left: Math.round(x),
            top: Math.round(y)
        });

        $previewer.show();

        var view = $previewer[0].querySelector('.highlight');
        if (view) {
            view.scrollIntoView();
        }
        previewLive = true;
    }

    $previewer.delegate('a', 'click', function(e) {
        var $a = $(e.target).closest('a');
        var href = $a.prop('href');
        if (window.confirm(minder.getLang('ui.redirect', href))) {
            window.open(href, '_blank');
        }
        e.preventDefault();
    });

    return {
        preview: preview,
        hide: function() {
            $previewer.hide();
        }
    };
});
/**
 * @fileOverview
 *
 * 添加和修改优先级标签
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/priority', function(minder) {

    var $commandbuttonset = minder.getUI('widget/commandbuttonset');
    var $tabs = minder.getUI('ribbon/tabs');

    var $priorityPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.priority')
    }).appendTo($tabs.idea);

    $commandbuttonset.generate('priority', [1, 2, 3, 4, 5, 6, 7, 8, 9, 0].map(function(p) {
        return {
            label: p,
            text: minder.getLang('ui.priority') + p,
            value: p,
            className: ['priority', p].join('-')
        };
    })).appendTo($priorityPanel);

    return $priorityPanel;
});
/**
 * @fileOverview
 *
 * 添加和修改进度标签
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/progress', function(minder) {

    var $commandbuttonset = minder.getUI('widget/commandbuttonset');
    var $tabs = minder.getUI('ribbon/tabs');

    var $progressPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.progress')
    }).appendTo($tabs.idea);

    $commandbuttonset.generate('progress', [1, 2, 3, 4, 5, 6, 7, 8, 9, 0].map(function(p) {
        return {
            label: p,
            text: minder.getLang('ui.progress.p' + p),
            value: p,
            className: ['progress', p].join('-')
        };
    })).appendTo($progressPanel);

    return $progressPanel;
});
/**
 * @fileOverview
 *
 * 添加和管理资源标签
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/idea/resource', function(minder) {

    var $commandbuttonset = minder.getUI('widget/commandbuttonset');
    var $tabs = minder.getUI('ribbon/tabs');

    var $resourcePanel = new FUI.LabelPanel({
        label: minder.getLang('panels.resource'),
        id: 'resource-panel'
    }).appendTo($tabs.idea);

    var $addInput = new FUI.Input().appendTo($resourcePanel);

    $addInput.getElement().type = 'text';

    var $addButton = new FUI.Button({
        label: '添加'
    }).appendTo($resourcePanel);

    var $resourceDrop = new FUI.DropPanel().appendTo($resourcePanel);
    var $dropContainer = $($resourceDrop.getPanelElement());
    var $ul = $('<ul></ul>').addClass('resource-list').appendTo($dropContainer);
    var $list = [];

    function addResource() {
        var resource = $addInput.getValue();
        var origin = minder.queryCommandValue('resource');
        if (/\S/.test(resource)) {
            if (!~origin.indexOf(resource)) origin.push(resource);
            origin.sort();
            minder.execCommand('resource', origin);
        }
        $addInput.setValue(null);
        update();
        $addInput.focus();
    }

    $addInput.on('inputcomplete', function(e) {
        addResource();
    });

    $addButton.on('click', addResource);

    $dropContainer.delegate('input[type=checkbox]', 'change', function() {
        minder.execCommand('resource', $dropContainer.find('input[type=checkbox]:checked').map(function(index, chk) {
            return $(chk).data('resource');
        }).toArray());
        update();
    });

    function hash(resource, used) {
        return [resource.join(','), used.join(',')].join(';');
    }

    function changed(resource, used) {
        var currentHash = hash(resource, used);
        if (currentHash == changed.lastHash) return false;
        changed.lastHash = currentHash;
        return true;
    }

    function update() {
        var resource = minder.queryCommandValue('resource');
        var used = minder.getUsedResource();

        used.sort();

        switch (minder.queryCommandState('resource')) {
            case 0:
                $addInput.enable();
                $addButton.enable();
                $resourceDrop.enable();
                $ul.find('input[type=checkbox]').removeAttr('disabled');
                break;
            case -1:
                $addInput.disable();
                $addButton.disable();
                $resourceDrop.disable();
                $ul.find('input[type=checkbox]').attr('disabled', true);
                break;
        }

        if (!changed(resource, used)) return;

        var delta = used.length - $ul.children().length;
        while (delta--) $ul.append('<li><label><input type="checkbox" /><span></span></label></li>');
        while (++delta) $ul.children().first().remove();

        used.forEach(function(name, index) {
            var $li = $ul.children().eq(index);
            var $label = $li.find('label');
            var $chk = $label.find('input');
            var $span = $label.find('span');

            $chk.data('resource', name);
            $chk.prop('checked', ~resource.indexOf(name));

            $span.text(name);
            var color = minder.getResourceColor(name);

            $li.css({
                color: color.dec('l', 60).toString(),
                backgroundColor: ~resource.indexOf(name) ? color : color.dec('a', 0.85).toRGBA()
            });
        });
    }

    minder.on('interactchange', update);

    return $resourcePanel;
});
/**
 * @fileOverview
 *
 * 模板选择
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/appearence/template', function(minder) {

    var $commandselectmenu = minder.getUI('widget/commandselectmenu');
    var $tabs = minder.getUI('ribbon/tabs');

    var $templatePanel = new FUI.LabelPanel({
        id: 'template-panel',
        label: minder.getLang('panels.template')
    });


    var templateList = KityMinder.Utils.keys(KityMinder.getTemplateList());
    var $templateSelect = $commandselectmenu.generate('template', templateList, 2);

    $tabs.appearence.appendWidget($templatePanel);
    $templatePanel.appendWidget($templateSelect);

    return $templatePanel;
});
/**
 * @fileOverview
 *
 * 皮肤选择
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/appearence/theme', function(minder) {

    var $commandselectmenu = minder.getUI('widget/commandselectmenu');
    var $tabs = minder.getUI('ribbon/tabs');

    var $themePanel = new FUI.LabelPanel({
        id: 'theme-panel',
        label: minder.getLang('panels.theme')
    });

    var themeList = KityMinder.Utils.keys(KityMinder.getThemeList());
    var $themeSelect = $commandselectmenu.generate('theme', themeList.map(function(theme) {
        var style = KityMinder._themes[theme];
        return {
            clazz: 'Button',
            label: {
                text: minder.getLang('theme.' + theme),
                style: {
                    background: style['root-background'],
                    color: style['root-color'],
                    borderRadius: style['root-radius'] / 2
                }
            },
            text: minder.getLang('theme.' + theme),
            value: theme,
            className: ['theme', theme].join(' ')
        };
    }));

    $tabs.appearence.appendWidget($themePanel);
    $themePanel.appendWidget($themeSelect);

    minder.on('themechange', function(e) {
        $('#content-wrapper').css('background', minder.getStyle('background'));
    });

    return $themePanel;
});
/**
 * @fileOverview
 *
 * 布局面板
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/appearence/layout', function(minder) {

    var $tabs = minder.getUI('ribbon/tabs');
    // var $commandbuttonset = minder.getUI('widget/commandbuttonset');
    var $commandbutton = minder.getUI('widget/commandbutton');

    var $layoutPanel = new FUI.LabelPanel({
        id: 'layout-panel',
        label: minder.getLang('panels.layout')
    }).appendTo($tabs.appearence);

    // var $layoutSelect = new FUI.DropPanel({
    //     id: 'layout-select'
    // }).appendTo($layoutPanel);

    // var layoutList = KityMinder.Utils.keys(KityMinder.getLayoutList());

    // $layoutSelect.appendWidget($commandbuttonset.generate('layout', layoutList));

    $commandbutton.generate('resetlayout').appendTo($layoutPanel).addClass('large');

    return $layoutPanel;
});

/**
 * @fileOverview
 *
 * 样式控制
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/appearence/style', function(minder) {

    var $tabs = minder.getUI('ribbon/tabs');
    var $commandbutton = minder.getUI('widget/commandbutton');

    var $stylePanel = new FUI.LabelPanel({
        label: minder.getLang('panels.style')
    }).appendTo($tabs.appearence);

    $commandbutton.generate('clearstyle').addClass('large').appendTo($stylePanel);

    var $styleClipPanel = new FUI.Panel({
        column: true
    }).appendTo($stylePanel);

    $commandbutton.generate('copystyle').appendTo($styleClipPanel);
    $commandbutton.generate('pastestyle').appendTo($styleClipPanel);

    return $stylePanel;
});
/**
 * @fileOverview
 *
 * 字体设置（字体字号加粗斜体）
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/appearence/font', function(minder) {

    var $tabs = minder.getUI('ribbon/tabs');
    var commandinputmenu = minder.getUI('widget/commandinputmenu');
    var commandbutton = minder.getUI('widget/commandbutton');

    var $fontPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.font'),
        id: 'font-panel'
    });

    var $leftPanel = new FUI.Panel({
        column: true
    });

    var $rightPanel = new FUI.Panel({
        column: true
    });

    var $fontFamilyMenu = commandinputmenu.generate('fontfamily', minder.getOptions('fontfamily').map(function(ff) {
        return {
            label: {
                text: ff.name,
                style: {
                    fontFamily: ff.val
                }
            },
            text: ff.name,
            value: ff.val
        };
    }));

    var $fontSizeMenu = commandinputmenu.generate('fontsize', minder.getOptions('fontsize').map(function(fs) {
        return {
            label: {
                text: fs,
                style: {
                    fontSize: fs
                }
            },
            text: fs,
            value: fs
        };
    }));

    $leftPanel.appendWidgets([$fontFamilyMenu, $fontSizeMenu]);

    var $boldButton = commandbutton.generate('bold');
    var $italicButton = commandbutton.generate('italic');

    $rightPanel.appendWidgets([$boldButton, $italicButton]);

    $fontPanel.appendWidgets([$leftPanel, $rightPanel]);

    $tabs.appearence.appendWidget($fontPanel);
});
/**
 * @fileOverview
 *
 * 节点颜色设置（包括和背景）
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/appearence/color', function(minder) {

    function generateSerisColor() {
        return ['#e75d66', '#fac75b', '#99ca6a', '#00c5ad', '#3bbce0', '#c9ced1', '#425b71', 'white'];
    }

    var $commandbuttonset = minder.getUI('widget/commandbuttonset');
    var $tabs = minder.getUI('ribbon/tabs');

    var $colorPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.color')
    }).appendTo($tabs.appearence);

    var $backgroundPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.background')
    }).appendTo($tabs.appearence);

    var foreColorList = generateSerisColor();

    $colorPanel.appendWidget($commandbuttonset.generate('forecolor', foreColorList.map(function(color) {
        return {
            icon: {
                style: {
                    background: color
                }
            },
            label: color,
            text: color,
            value: color
        };
    })).addClass('color-picker'));

    $backgroundPanel.appendWidget($commandbuttonset.generate('background', foreColorList.map(function(color) {
        return {
            icon: {
                style: {
                    background: color
                }
            },
            label: color,
            text: color,
            value: color
        };
    })).addClass('color-picker'));

    return {
        color: $colorPanel,
        background: $backgroundPanel
    };
});

/**
 * @fileOverview
 *
 * 全屏无打扰模式
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('ribbon/view/fullscreen', function(minder) {

    var $commandbutton = minder.getUI('widget/commandbutton');
    var $tabs = minder.getUI('ribbon/tabs');
    var notice = minder.getUI('widget/notice');

    var $fullscreenPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.level'),
        column: true
    }).appendTo($tabs.view);

    var $fullscreenButton = $commandbutton
        .generate('fullscreen', fullscreen)
        .addClass('large')
        .appendTo($fullscreenPanel);

    function fullscreen() {
        if ($('#content-wrapper').toggleClass('fullscreen').hasClass('fullscreen')) {
            notice.info(minder.getLang('ui.fullscreen_exit_hint'), false, 4000);
        }
    }

    minder.addShortcut('F11', fullscreen);
    minder.addShortcut('Esc', function () {
        if ($('#content-wrapper').hasClass('fullscreen')) {
            $('#content-wrapper').removeClass('fullscreen');
        }
    });

    return $fullscreenButton;
});

/**
 * @fileOverview
 *
 * 切换展开层次
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */
KityMinder.registerUI('ribbon/view/level', function(minder) {

    var $commandbutton = minder.getUI('widget/commandbutton');
    var $tabs = minder.getUI('ribbon/tabs');

    var $levelPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.level'),
        column: true
    }).appendTo($tabs.view);

    var $levelButtonMenu = new FUI.ButtonMenu({
        id: 'level-button-menu',
        text: minder.getLang('ui.level'),
        layout: 'bottom',
        buttons: [{}, {
            label: minder.getLang('ui.expandtoleaf')
        }],
        menu: {
            items: [1, 2, 3, 4, 5, 6].map(function(level) {
                return {
                    label: minder.getLang('ui.command.expandtolevel' + level),
                    value: level
                };
            })
        }
    }).appendTo($levelPanel);

    $levelButtonMenu.on('buttonclick', function() {
        minder.execCommand('expandtolevel', 9999);
    });

    $levelButtonMenu.on('select', function(e, info) {
        minder.execCommand('expandtolevel', info.value);
    });
});

/**
 * @fileOverview
 *
 * 节点选择功能
 *
 * @author: techird
 * @copyright: Baidu FEX, 2014
 */

KityMinder.registerUI('ribbon/view/select', function(minder) {

    var $tabs = minder.getUI('ribbon/tabs');

    var $selectPanel = new FUI.LabelPanel({
        label: minder.getLang('panels.level'),
        column: true
    }).appendTo($tabs.view);

    var $selectButtonMenu = new FUI.ButtonMenu({
        id: 'select-button-menu',
        text: minder.getLang('ui.select'),
        layout: 'bottom',
        buttons: [{}, {
            label: minder.getLang('ui.selectall')
        }],
        menu: {
            items: ['revert', 'siblings', 'level', 'path', 'tree'].map(function(mode) {
                return {
                    label: minder.getLang('ui.select' + mode),
                    value: mode
                };
            })
        }
    }).appendTo($selectPanel);

    var select = {
        all: function() {
            var selection = [];
            minder.getRoot().traverse(function(node) {
                selection.push(node);
            });
            minder.select(selection, true);
        },
        revert: function() {
            var selected = minder.getSelectedNodes();
            var selection = [];
            minder.getRoot().traverse(function(node) {
                if (selected.indexOf(node) == -1) {
                    selection.push(node);
                }
            });
            minder.select(selection, true);
        },
        siblings: function() {
            var selected = minder.getSelectedNodes();
            var selection = [];
            selected.forEach(function(node) {
                if (!node.parent) return;
                node.parent.children.forEach(function(sibling) {
                    if (selection.indexOf(sibling) == -1) selection.push(sibling);
                });
            });
            minder.select(selection, true);
        },
        level: function() {
            var selectedLevel = minder.getSelectedNodes().map(function(node) {
                return node.getLevel();
            });
            var selection = [];
            minder.getRoot().traverse(function(node) {
                if (selectedLevel.indexOf(node.getLevel()) != -1) {
                    selection.push(node);
                }
            });
            minder.select(selection, true);
        },
        path: function() {
            var selected = minder.getSelectedNodes();
            var selection = [];
            selected.forEach(function(node) {
                while(node && selection.indexOf(node) == -1) {
                    selection.push(node);
                    node = node.parent;
                }
            });
            minder.select(selection, true);
        },
        tree: function() {
            var selected = minder.getSelectedNodes();
            var selection = [];
            selected.forEach(function(parent) {
                parent.traverse(function(node) {
                    if (selection.indexOf(node) == -1) selection.push(node);
                });
            });
            minder.select(selection, true);
        }
    };

    $selectButtonMenu.on('buttonclick', select.all);
    $selectButtonMenu.on('select', function(e, info) {
        select[info.value]();
    });
});

})(window)
//# sourceMappingURL=kityminder.edit.js.map